From 3c644f24a7fa35bae5b419b169c27ec2c99dfcce Mon Sep 17 00:00:00 2001 From: Raman Gupta Date: Tue, 27 Oct 2026 17:11:42 -0400 Subject: [PATCH] chore: clear the Kotlin compiler warnings - GitHub.kt: drop a safe call on a review already known non-null. - WorktreeOps.kt: drop an `own != null` check that `merged` already implies. - Clipboard: move the copy controls from the deprecated LocalClipboardManager to LocalClipboard, through a small Clipboard.setText helper that wraps an AWT StringSelection (the desktop ClipEntry constructor is still experimental, so the opt-in lives there). - Main.kt: replace the deprecated painterResource with the app icon decoded once from the classpath via Skia, rather than pulling in the Compose resources library for one PNG. It loads through our own class, so it no longer depends on the thread's context class loader. Co-Authored-By: Claude Signed-off-by: Raman Gupta --- src/main/kotlin/com/gitvantage/app/GitHub.kt | 2 +- .../kotlin/com/gitvantage/git/WorktreeOps.kt | 2 +- .../kotlin/com/gitvantage/ui/Components.kt | 33 ++++++++++++++----- src/main/kotlin/com/gitvantage/ui/Main.kt | 23 +++++++++++-- src/main/kotlin/com/gitvantage/ui/Popups.kt | 7 ++-- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/gitvantage/app/GitHub.kt b/src/main/kotlin/com/gitvantage/app/GitHub.kt index 8294a50..50e6d2c 100644 --- a/src/main/kotlin/com/gitvantage/app/GitHub.kt +++ b/src/main/kotlin/com/gitvantage/app/GitHub.kt @@ -669,7 +669,7 @@ object GitHub { val reviewIsNewest = lastReview != null && lastReview.submittedAt.toMillis() >= lastComment?.createdAt.toMillis() val reviewedMine = viewerDidAuthor && reviewIsNewest && !lastReviewBy.isLogin(viewer) && - lastReviewBy.isNotEmpty() && lastReview?.reactionGroups.orEmpty().none { it.viewerHasReacted } + lastReviewBy.isNotEmpty() && lastReview.reactionGroups.orEmpty().none { it.viewerHasReacted } val mentioned = lastComment != null && !acknowledged && mentionsMe(lastComment.bodyText, viewer) // "Someone replied on my own thread and I haven't answered." Only counts when the last diff --git a/src/main/kotlin/com/gitvantage/git/WorktreeOps.kt b/src/main/kotlin/com/gitvantage/git/WorktreeOps.kt index a1d8f9d..87f7c03 100644 --- a/src/main/kotlin/com/gitvantage/git/WorktreeOps.kt +++ b/src/main/kotlin/com/gitvantage/git/WorktreeOps.kt @@ -177,7 +177,7 @@ object WorktreeOps { branchMerged = merged, branchEmpty = contained && !merged, tipBranch = tipBranch, - deletableUpstream = if (merged && own != null) BranchOps.deletableUpstream(dir, own) else null, + deletableUpstream = if (merged) BranchOps.deletableUpstream(dir, own) else null, ) } } diff --git a/src/main/kotlin/com/gitvantage/ui/Components.kt b/src/main/kotlin/com/gitvantage/ui/Components.kt index 47ec3b9..f8e0694 100644 --- a/src/main/kotlin/com/gitvantage/ui/Components.kt +++ b/src/main/kotlin/com/gitvantage/ui/Components.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -50,8 +51,10 @@ import androidx.compose.ui.input.pointer.isMetaPressed import androidx.compose.ui.input.pointer.isShiftPressed import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.Clipboard +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -65,12 +68,14 @@ import com.gitvantage.app.MonoFont import com.gitvantage.app.Tokens import com.gitvantage.app.UiFont import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.milliseconds import androidx.compose.foundation.Canvas import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.Dp import kotlin.math.cos import kotlin.math.sin +import java.awt.datatransfer.StringSelection /** A 1px bottom hairline, like CSS `border-bottom`. */ fun Modifier.drawBottomBorder(color: Color): Modifier = this.drawBehind { @@ -393,6 +398,15 @@ fun InfoTip(text: String, modifier: Modifier = Modifier) { } } +/** + * Put [text] on the system clipboard as plain text. + * + * [Clipboard] only takes a [ClipEntry], and on desktop the one way to build that is around an AWT + * `Transferable` — a constructor Compose still marks experimental, hence the opt-in. + */ +@OptIn(ExperimentalComposeUiApi::class) +suspend fun Clipboard.setText(text: String) = setClipEntry(ClipEntry(StringSelection(text))) + /** * A pill that puts [value] on the clipboard and confirms it in place — the label swaps to * "✓ Copied" for a moment. The confirmation is local rather than a toast because the toast @@ -405,7 +419,8 @@ fun InfoTip(text: String, modifier: Modifier = Modifier) { */ @Composable fun CopyPill(value: String, label: String = "Copy", modifier: Modifier = Modifier) { - val clipboard = LocalClipboardManager.current + val clipboard = LocalClipboard.current + val scope = rememberCoroutineScope() var copied by remember(value) { mutableStateOf(false) } LaunchedEffect(copied) { if (copied) { delay(1200.milliseconds); copied = false } } val shape = RoundedCornerShape(6.dp) @@ -414,7 +429,7 @@ fun CopyPill(value: String, label: String = "Copy", modifier: Modifier = Modifie .background(if (copied) Tokens.tintGreen else Tokens.tintBlue, shape) .border(1.dp, if (copied) Tokens.okBorder else Tokens.accentBorder, shape) .pointerHoverIcon(PointerIcon.Hand) - .onTap { clipboard.setText(AnnotatedString(value)); copied = true } + .onTap { scope.launch { clipboard.setText(value); copied = true } } .padding(horizontal = 9.dp, vertical = 2.dp), contentAlignment = Alignment.Center, ) { @@ -492,14 +507,15 @@ fun RowAction(label: String, danger: Boolean = false, onClick: () -> Unit) { */ @Composable fun CopyAction(value: String, label: String = "Copy") { - val clipboard = LocalClipboardManager.current + val clipboard = LocalClipboard.current + val scope = rememberCoroutineScope() var copied by remember(value) { mutableStateOf(false) } LaunchedEffect(copied) { if (copied) { delay(1200.milliseconds); copied = false } } Txt( if (copied) "✓ Copied" else label, 11.5.sp, if (copied) Tokens.addFg else Tokens.accent, FontWeight.Medium, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand) - .onTap { clipboard.setText(AnnotatedString(value)); copied = true }, + .onTap { scope.launch { clipboard.setText(value); copied = true } }, ) } @@ -524,13 +540,14 @@ fun CopyTarget( modifier: Modifier = Modifier, content: @Composable (copied: Boolean) -> Unit, ) { - val clipboard = LocalClipboardManager.current + val clipboard = LocalClipboard.current + val scope = rememberCoroutineScope() var copied by remember(value) { mutableStateOf(false) } LaunchedEffect(copied) { if (copied) { delay(1200.milliseconds); copied = false } } HoverTip( tip, modifier.pointerHoverIcon(PointerIcon.Hand) - .onTap { clipboard.setText(AnnotatedString(value)); copied = true }, + .onTap { scope.launch { clipboard.setText(value); copied = true } }, ) { content(copied) } } diff --git a/src/main/kotlin/com/gitvantage/ui/Main.kt b/src/main/kotlin/com/gitvantage/ui/Main.kt index 09e0d18..f38507a 100644 --- a/src/main/kotlin/com/gitvantage/ui/Main.kt +++ b/src/main/kotlin/com/gitvantage/ui/Main.kt @@ -34,7 +34,9 @@ import androidx.compose.ui.input.key.isMetaPressed import androidx.compose.ui.input.key.isShiftPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp @@ -56,6 +58,7 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.debounce import kotlin.time.Duration.Companion.milliseconds +import org.jetbrains.skia.Image as SkiaImage /** * `--smoke-test` runs the subsystem checks in [runSmokeChecks] and exits, without opening a window. @@ -97,6 +100,20 @@ private fun adoptContextClassLoader() { } } +/** + * The app icon, for both the window and the title bar. + * + * Decoded straight from the classpath rather than through Compose's `painterResource`, which is + * deprecated in favour of the Compose resources library — a generated `Res` class and a + * `composeResources` folder, for one PNG. Read through our own class, so it doesn't depend on the + * thread's context class loader either (see [adoptContextClassLoader]). + */ +private val appIcon: Painter by lazy { + val bytes = checkNotNull(AppState::class.java.getResourceAsStream("/app-icon.png")) { "app-icon.png missing" } + .use { it.readAllBytes() } + BitmapPainter(SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap()) +} + @OptIn(FlowPreview::class) private fun runApp() = nucleusApplication(backend = NucleusBackend.Tao) { adoptContextClassLoader() @@ -131,7 +148,7 @@ private fun runApp() = nucleusApplication(backend = NucleusBackend.Tao) { state = windowState, visible = windowVisible, title = "GitVantage", - icon = painterResource("app-icon.png"), // taskbar / window icon (X11/Win/macOS; Wayland uses app_id) + icon = appIcon, // taskbar / window icon (X11/Win/macOS; Wayland uses app_id) onPreviewKeyEvent = ::zoomShortcut, ) { // Everything the window draws — the Nucleus-drawn title bar included — sizes itself @@ -153,7 +170,7 @@ private fun runApp() = nucleusApplication(backend = NucleusBackend.Tao) { horizontalArrangement = Arrangement.spacedBy(10.dp), ) { Image( - painter = painterResource("app-icon.png"), + painter = appIcon, contentDescription = "GitVantage", modifier = Modifier.size(22.dp).clip(RoundedCornerShape(6.dp)), ) diff --git a/src/main/kotlin/com/gitvantage/ui/Popups.kt b/src/main/kotlin/com/gitvantage/ui/Popups.kt index af0da3f..99cf27d 100644 --- a/src/main/kotlin/com/gitvantage/ui/Popups.kt +++ b/src/main/kotlin/com/gitvantage/ui/Popups.kt @@ -67,8 +67,7 @@ import com.gitvantage.app.UiFont import com.gitvantage.app.UiScale import com.gitvantage.model.Meta import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import com.gitvantage.app.DiagLog @@ -513,7 +512,7 @@ private fun DiagnosticsSection(state: AppState) { */ @Composable private fun CopyReportPill(state: AppState) { - val clipboard = LocalClipboardManager.current + val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() var copied by remember { mutableStateOf(false) } LaunchedEffect(copied) { if (copied) { delay(1200.milliseconds); copied = false } } @@ -525,7 +524,7 @@ private fun CopyReportPill(state: AppState) { .pointerHoverIcon(PointerIcon.Hand) .onTap { scope.launch { - clipboard.setText(AnnotatedString(state.diagnosticsReport())) + clipboard.setText(state.diagnosticsReport()) copied = true } }