fix: issue in which UI callback listeners were leaking

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-11-23 14:14:52 +01:00
parent 20a066dabb
commit 4d1fffe42a
2 changed files with 270 additions and 275 deletions

View File

@ -14,7 +14,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -65,6 +64,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -162,10 +162,7 @@ private fun pxAsDp(px: Int): Dp {
).dp ).dp
} }
@OptIn( @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class,
ExperimentalAnimationApi::class
)
@Composable @Composable
fun ChannelScreen( fun ChannelScreen(
channelId: String, channelId: String,
@ -173,25 +170,30 @@ fun ChannelScreen(
useDrawer: Boolean, useDrawer: Boolean,
viewModel: ChannelScreenViewModel = hiltViewModel() viewModel: ChannelScreenViewModel = hiltViewModel()
) { ) {
// Setup // <editor-fold desc="State and effects">
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.startListening(createUiCallbackListener = true) viewModel.listenToWsEvents()
} }
// Load/switch channel DisposableEffect(Unit) {
val job = scope.launch { viewModel.listenToUiCallbacks() }
onDispose {
job.cancel()
}
}
// </editor-fold>
// <editor-fold desc="Load/switch channel">
val channelPermissions by rememberChannelPermissions(channelId, viewModel.ensuredSelfMember) val channelPermissions by rememberChannelPermissions(channelId, viewModel.ensuredSelfMember)
LaunchedEffect(channelId) { LaunchedEffect(channelId) {
viewModel.switchChannel(channelId) viewModel.switchChannel(channelId)
} }
// </editor-fold>
// Keyboard height // <editor-fold desc="Keyboard height handling">
val imeTarget = WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) val imeTarget = WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current)
val navigationBarsInset = WindowInsets.navigationBars.getBottom(LocalDensity.current) val navigationBarsInset = WindowInsets.navigationBars.getBottom(LocalDensity.current)
val imeCurrentInset = WindowInsets.ime.getBottom(LocalDensity.current) val imeCurrentInset = WindowInsets.ime.getBottom(LocalDensity.current)
@ -211,9 +213,8 @@ fun ChannelScreen(
imeInTransition = false imeInTransition = false
} }
} }
// </editor-fold>
// Attachment handling // <editor-fold desc="Attachment handling">
val processFileUri: (Uri, String?) -> Unit = remember { val processFileUri: (Uri, String?) -> Unit = remember {
{ uri, pickerIdentifier -> { uri, pickerIdentifier ->
DocumentFile.fromSingleUri(context, uri)?.let { file -> DocumentFile.fromSingleUri(context, uri)?.let { file ->
@ -278,9 +279,8 @@ fun ChannelScreen(
} }
} }
} }
// </editor-fold>
// UI elements // <editor-fold desc="UI elements">
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val isScrolledToBottom = remember(lazyListState) { val isScrolledToBottom = remember(lazyListState) {
@ -328,9 +328,8 @@ fun ChannelScreen(
} }
} }
} }
// </editor-fold>
// Sheets // <editor-fold desc="Sheets">
var channelInfoSheetShown by remember { mutableStateOf(false) } var channelInfoSheetShown by remember { mutableStateOf(false) }
if (channelInfoSheetShown) { if (channelInfoSheetShown) {
val channelInfoSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val channelInfoSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
@ -399,9 +398,8 @@ fun ChannelScreen(
} }
} }
} }
// </editor-fold>
// Begin UI composition // <editor-fold desc="Begin UI composition">
Scaffold( Scaffold(
contentWindowInsets = WindowInsets( contentWindowInsets = WindowInsets(
left = 0, left = 0,
@ -1033,4 +1031,5 @@ fun ChannelScreen(
} }
} }
} }
// </editor-fold>
} }

View File

@ -498,8 +498,7 @@ class ChannelScreenViewModel @Inject constructor(
ackChannel(channel?.id ?: return, messageId) ackChannel(channel?.id ?: return, messageId)
} }
suspend fun startListening(createUiCallbackListener: Boolean = true) { suspend fun listenToWsEvents() {
viewModelScope.launch {
withContext(RevoltAPI.realtimeContext) { withContext(RevoltAPI.realtimeContext) {
flow { flow {
while (true) { while (true) {
@ -689,7 +688,7 @@ class ChannelScreenViewModel @Inject constructor(
is RealtimeSocketFrames.Reconnected -> { is RealtimeSocketFrames.Reconnected -> {
Log.d("ChannelScreen", "Reconnected to WS.") Log.d("ChannelScreen", "Reconnected to WS.")
loadMessages(50, ignoreExisting = true) loadMessages(50, ignoreExisting = true)
startListening(createUiCallbackListener = false) listenToWsEvents()
} }
} }
}.catch { }.catch {
@ -698,8 +697,7 @@ class ChannelScreenViewModel @Inject constructor(
} }
} }
if (createUiCallbackListener) { suspend fun listenToUiCallbacks() {
viewModelScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
UiCallbacks.uiCallbackFlow.onEach { UiCallbacks.uiCallbackFlow.onEach {
Log.d("ChannelScreen", "Received UI callback: $it") Log.d("ChannelScreen", "Received UI callback: $it")
@ -750,8 +748,6 @@ class ChannelScreenViewModel @Inject constructor(
}.launchIn(this) }.launchIn(this)
} }
} }
}
}
private suspend fun updateItems(newItems: List<ChannelScreenItem>) { private suspend fun updateItems(newItems: List<ChannelScreenItem>) {
// Spec https://wiki.rvlt.gg/index.php/Text_Channel_(UI)#Message_Grouping_Algorithm // Spec https://wiki.rvlt.gg/index.php/Text_Channel_(UI)#Message_Grouping_Algorithm