diff --git a/app/src/main/kotlin/com/darkwisp/app/ui/screen/ComposeScreen.kt b/app/src/main/kotlin/com/darkwisp/app/ui/screen/ComposeScreen.kt index ba1297b..d7474f8 100644 --- a/app/src/main/kotlin/com/darkwisp/app/ui/screen/ComposeScreen.kt +++ b/app/src/main/kotlin/com/darkwisp/app/ui/screen/ComposeScreen.kt @@ -1219,7 +1219,7 @@ fun ComposeScreen( resolvedEmojis = resolvedEmojis ) }, - enabled = !publishing && !isMiningBusy, + enabled = !publishing && !isMiningBusy && (content.text.isNotBlank() || uploadedUrls.isNotEmpty()), modifier = Modifier.fillMaxWidth() ) { Text( diff --git a/app/src/main/kotlin/com/darkwisp/app/ui/screen/ThreadScreen.kt b/app/src/main/kotlin/com/darkwisp/app/ui/screen/ThreadScreen.kt index c2e4e92..7ebbf98 100644 --- a/app/src/main/kotlin/com/darkwisp/app/ui/screen/ThreadScreen.kt +++ b/app/src/main/kotlin/com/darkwisp/app/ui/screen/ThreadScreen.kt @@ -20,8 +20,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -29,6 +31,8 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.ui.res.stringResource import com.darkwisp.app.R import androidx.compose.material3.TopAppBar @@ -49,6 +53,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp @@ -208,6 +215,7 @@ fun ThreadScreen( Toast.makeText(zapDisabledContext, zapDisabledMessage, Toast.LENGTH_SHORT).show() } + val focalEvent = flatThread.firstOrNull()?.first Scaffold( contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = { @@ -222,6 +230,12 @@ fun ThreadScreen( containerColor = MaterialTheme.colorScheme.surface ) ) + }, + bottomBar = { + ThreadReplyBar( + enabled = focalEvent != null, + onClick = { focalEvent?.let { onReply(it) } } + ) } ) { padding -> if (isLoading && flatThread.isEmpty()) { @@ -278,23 +292,40 @@ fun ThreadScreen( val userZapPollVote = remember(pollVoteVersion, event.id) { if (event.kind == 6969) eventRepo.getUserZapPollVote(event.id) else null } - val indentDp = 12 - val clampedDepth = min(depth, 8) - val lineColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.45f) + val indentStepDp = 12.dp + val clampedDepth = min(depth, 5) + val cornerRadiusDp = 8.dp + val lineColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f) + val showConnector = depth > 0 Box( modifier = Modifier .fillMaxWidth() .drawBehind { - val indentPx = indentDp.dp.toPx() - for (level in 0 until clampedDepth) { - val x = level * indentPx + indentPx / 2f - drawLine( - color = lineColor, - start = Offset(x, 0f), - end = Offset(x, size.height), - strokeWidth = 1.5.dp.toPx() - ) - } + if (!showConnector) return@drawBehind + val lineX = (clampedDepth * indentStepDp.toPx()) - 8.dp.toPx() + 1.dp.toPx() + val r = cornerRadiusDp.toPx() + val strokePx = 1.dp.toPx() + drawLine( + color = lineColor, + start = Offset(lineX, 0f), + end = Offset(lineX, size.height - r), + strokeWidth = strokePx + ) + drawArc( + color = lineColor, + startAngle = 90f, + sweepAngle = 90f, + useCenter = false, + topLeft = Offset(lineX, size.height - 2f * r), + size = Size(2f * r, 2f * r), + style = Stroke(width = strokePx, cap = StrokeCap.Round) + ) + drawLine( + color = lineColor, + start = Offset(lineX + r, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokePx + ) } ) { if (isGalleryEvent(event)) { @@ -340,7 +371,8 @@ fun ThreadScreen( nip05Repo = nip05Repo, onQuotedNoteClick = onQuotedNoteClick, noteActions = noteActions, - modifier = Modifier.padding(start = (clampedDepth * indentDp).dp) + showDivider = !showConnector, + modifier = Modifier.padding(start = (clampedDepth * indentStepDp.value).dp) ) } else { PostCard( @@ -398,7 +430,8 @@ fun ThreadScreen( zapPollTotalSats = zapPollTotalSats, userZapPollVote = userZapPollVote, onZapPollVote = { idx -> onZapPollVote(event.id, idx) }, - modifier = Modifier.padding(start = (clampedDepth * indentDp).dp) + showDivider = !showConnector, + modifier = Modifier.padding(start = (clampedDepth * indentStepDp.value).dp) ) } } @@ -578,3 +611,50 @@ private fun SpamToggle(count: Int, expanded: Boolean, onToggle: () -> Unit) { } } +@Composable +private fun ThreadReplyBar( + enabled: Boolean, + onClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + ) { + HorizontalDivider( + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f), + thickness = 0.5.dp + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Surface( + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), + shape = RoundedCornerShape(18.dp), + modifier = Modifier + .fillMaxWidth() + .clickable(enabled = enabled, onClick = onClick) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp) + ) { + Text( + text = "Reply…", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + imageVector = Icons.Outlined.Edit, + contentDescription = "Reply", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/com/darkwisp/app/viewmodel/ThreadViewModel.kt b/app/src/main/kotlin/com/darkwisp/app/viewmodel/ThreadViewModel.kt index 0ae9999..de939c2 100644 --- a/app/src/main/kotlin/com/darkwisp/app/viewmodel/ThreadViewModel.kt +++ b/app/src/main/kotlin/com/darkwisp/app/viewmodel/ThreadViewModel.kt @@ -466,16 +466,9 @@ class ThreadViewModel : ViewModel() { scoreAuthorsAsync(pubkeysToScore) } - val myPubkey = currentUserPubkey for (children in parentToChildren.values) { children.sortWith(Comparator { a, b -> - val aIsOwn = myPubkey != null && a.pubkey == myPubkey - val bIsOwn = myPubkey != null && b.pubkey == myPubkey - if (aIsOwn != bIsOwn) { - if (aIsOwn) -1 else 1 - } else { - a.created_at.compareTo(b.created_at) - } + a.created_at.compareTo(b.created_at) }) }