diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index c89dd918d..5152a7f76 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -1204,9 +1204,11 @@ private fun NavGraphBuilder.lightningConnections( composableWithDefaultTransitions { val parentEntry = remember(it) { navController.getBackStackEntry(Routes.ConnectionsNav) } val viewModel = hiltViewModel(parentEntry) + val route = it.toRoute() ChannelDetailScreen( navController = navController, viewModel = viewModel, + channelId = route.channelId, ) } composableWithDefaultTransitions { @@ -1836,7 +1838,7 @@ sealed interface Routes { data object LightningConnections : Routes @Serializable - data object ChannelDetail : Routes + data class ChannelDetail(val channelId: String) : Routes @Serializable data object CloseConnection : Routes diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt index 04795bd4d..02b98fcb2 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.pulltorefresh.PullToRefreshBox @@ -85,19 +86,32 @@ import java.util.Locale fun ChannelDetailScreen( navController: NavController, viewModel: LightningConnectionsViewModel, + channelId: String, ) { val context = LocalContext.current val app = appViewModel ?: return val wallet = walletViewModel ?: return + LaunchedEffect(channelId) { + viewModel.findAndSelectChannel(channelId) + } + val selectedChannel by viewModel.selectedChannel.collectAsStateWithLifecycle() - val channel = selectedChannel ?: return + val channel = selectedChannel val uiState by viewModel.uiState.collectAsStateWithLifecycle() val paidOrders by viewModel.blocktankRepo.blocktankState.collectAsStateWithLifecycle() + val lightningState by wallet.lightningState.collectAsStateWithLifecycle() + + if (channel == null) { + Content( + channel = null, + onBack = { navController.popBackStack() }, + ) + return + } val isClosedChannel = uiState.closedChannels.any { it.details.channelId == channel.details.channelId } - val lightningState by wallet.lightningState.collectAsStateWithLifecycle() // Fetch transaction details for funding transaction if available LaunchedEffect(channel.details.fundingTxo?.txid) { @@ -108,8 +122,8 @@ fun ChannelDetailScreen( // Fetch activity timestamp for transfer activity with matching channel ID LaunchedEffect(channel.details.channelId) { - channel.details.channelId?.let { channelId -> - viewModel.fetchActivityTimestamp(channelId) + channel.details.channelId.let { id -> + viewModel.fetchActivityTimestamp(id) } } @@ -148,7 +162,7 @@ fun ChannelDetailScreen( @Suppress("CyclomaticComplexMethod") @Composable private fun Content( - channel: ChannelUi, + channel: ChannelUi?, blocktankOrders: List = emptyList(), cjitEntries: List = emptyList(), txTime: ULong? = null, @@ -161,36 +175,46 @@ private fun Content( onSupport: (Any) -> Unit = {}, onCloseConnection: () -> Unit = {}, ) { - // Check if the channel was opened via CJIT - val cjitEntry = cjitEntries.find { entry -> - entry.channel?.fundingTx?.id == channel.details.fundingTxo?.txid - } - - // Check if the channel was opened via blocktank order - val blocktankOrder = blocktankOrders.find { order -> - // real channel - if (channel.details.fundingTxo?.txid != null) { - order.channel?.fundingTx?.id == channel.details.fundingTxo?.txid - } else { - // fake channel - order.id == channel.details.channelId - } - } - - val order = blocktankOrder ?: cjitEntry - - val capacity = channel.details.channelValueSats.toLong() - val localBalance = channel.details.amountOnClose.toLong() - val remoteBalance = (channel.details.inboundCapacityMsat / 1000u).toLong() - val reserveBalance = (channel.details.unspendablePunishmentReserve ?: 0u).toLong() - ScreenColumn { AppTopBar( - titleText = channel.name, + titleText = channel?.name ?: "", onBackClick = onBack, actions = { DrawerNavIcon() }, ) + if (channel == null) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize(), + ) { + CircularProgressIndicator() + } + return@ScreenColumn + } + + // Check if the channel was opened via CJIT + val cjitEntry = cjitEntries.find { entry -> + entry.channel?.fundingTx?.id == channel.details.fundingTxo?.txid + } + + // Check if the channel was opened via blocktank order + val blocktankOrder = blocktankOrders.find { order -> + // real channel + if (channel.details.fundingTxo?.txid != null) { + order.channel?.fundingTx?.id == channel.details.fundingTxo?.txid + } else { + // fake channel + order.id == channel.details.channelId + } + } + + val order = blocktankOrder ?: cjitEntry + + val capacity = channel.details.channelValueSats.toLong() + val localBalance = channel.details.amountOnClose.toLong() + val remoteBalance = (channel.details.inboundCapacityMsat / 1000u).toLong() + val reserveBalance = (channel.details.unspendablePunishmentReserve ?: 0u).toLong() + PullToRefreshBox( isRefreshing = isRefreshing, onRefresh = onRefresh, @@ -628,6 +652,14 @@ private fun createSupportEmailIntent( return Intent(Intent.ACTION_SENDTO, uri) } +@Preview +@Composable +private fun PreviewLoadingState() { + AppThemeSurface { + Content(channel = null) + } +} + @Preview @Composable private fun PreviewOpenChannel() { diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt index f9611f6cc..857b1ac3c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt @@ -88,18 +88,13 @@ fun LightningConnectionsScreen( viewModel.refreshObservedState() viewModel.clearSelectedChannel() viewModel.clearTransactionDetails() - } - LaunchedEffect(navController.currentBackStackEntry) { val selectedChannelId = navController.previousBackStackEntry?.savedStateHandle?.get("selectedChannelId") - if (selectedChannelId == null) return@LaunchedEffect - - navController.previousBackStackEntry?.savedStateHandle?.remove("selectedChannelId") - delay(CHANNEL_SELECTION_DELAY_MS) - if (viewModel.findAndSelectChannel(selectedChannelId)) { - navController.navigate(Routes.ChannelDetail) { + if (selectedChannelId != null) { + navController.previousBackStackEntry?.savedStateHandle?.remove("selectedChannelId") + delay(CHANNEL_SELECTION_DELAY_MS) + navController.navigate(Routes.ChannelDetail(selectedChannelId)) { launchSingleTop = true - popUpTo(Routes.ConnectionsNav) { inclusive = false } } } } @@ -112,8 +107,7 @@ fun LightningConnectionsScreen( viewModel.zipLogsForSharing { uri -> context.shareZipFile(uri) } }, onClickChannel = { channelUi -> - viewModel.setSelectedChannel(channelUi) - navController.navigate(Routes.ChannelDetail) + navController.navigate(Routes.ChannelDetail(channelUi.details.channelId)) }, onRefresh = { viewModel.onPullToRefresh()