diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 33b97e735..c797f1ad9 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -51,6 +51,7 @@ import com.droiddeck.launcher.session.PerfMode import com.droiddeck.launcher.session.SessionPrefs import com.droiddeck.launcher.session.SessionEvents import com.droiddeck.launcher.session.SessionArtifacts +import com.droiddeck.launcher.session.SessionLogShare import com.droiddeck.launcher.session.SessionPhase import com.droiddeck.launcher.session.SessionPaths import com.droiddeck.launcher.wayland.HdrSupport @@ -338,6 +339,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } }, onBackground = { drawerOpen = false; moveTaskToBack(true) }, + onShareLogs = { drawerOpen = false; shareCurrentSessionLogs() }, onStop = { drawerOpen = false; SessionService.stop(this@SessionActivity); finish() }, onClose = { drawerOpen = false }, )) @@ -422,6 +424,30 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { if (SessionState.running && SessionState.mode == SessionService.MODE_STEAM) sendSteamGuide() } + private fun shareCurrentSessionLogs() { + val folder = SessionPaths.current() + if (folder == null || !folder.isDirectory) { + Toast.makeText(this, "No logs for this session.", Toast.LENGTH_LONG).show() + return + } + Thread({ + val zip = runCatching { SessionLogShare.zipFolder(this, folder) } + .onFailure { Log.w(TAG, "could not package current session logs", it) } + .getOrNull() + uiHandler.post { + if (zip == null) { + Toast.makeText(this, "Could not create the session log archive.", Toast.LENGTH_LONG).show() + } else { + runCatching { startActivity(SessionLogShare.shareIntent(this, zip)) } + .onFailure { + Log.w(TAG, "could not share current session logs", it) + Toast.makeText(this, "Could not share the session logs.", Toast.LENGTH_LONG).show() + } + } + } + }, "share-session-logs").start() + } + private fun readPrefs() { hudOn = SessionPrefs.hudEnabled(this) fillScreen = SessionPrefs.forceFullscreen(this) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionLogShare.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionLogShare.kt index a7a1cdf83..de07f18a7 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionLogShare.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionLogShare.kt @@ -22,12 +22,17 @@ object SessionLogShare { .maxByOrNull { it.name } /** Builds the zip (blocking). Returns null when there is no session to share. */ - fun zipLatest(context: Context): File? { - val folder = latest(context) ?: return null + fun zipLatest(context: Context): File? = latest(context)?.let { zipFolder(context, it) } + + /** Builds a zip for one specific session folder (blocking). */ + fun zipFolder(context: Context, folder: File): File? { + if (!folder.isDirectory) return null + val files = folder.walkTopDown().filter { it.isFile }.toList() + if (files.isEmpty()) return null val out = File(context.cacheDir, "shared-logs").apply { deleteRecursively(); mkdirs() } val zip = File(out, "DroidDeck-${folder.name}.zip") ZipOutputStream(zip.outputStream().buffered()).use { z -> - folder.walkTopDown().filter { it.isFile }.forEach { f -> + files.forEach { f -> z.putNextEntry(ZipEntry(folder.name + "/" + f.relativeTo(folder).path)) f.inputStream().use { it.copyTo(z) } z.closeEntry() diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index afbc86ef6..c7db1ff75 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -226,6 +226,7 @@ class DrawerActions( val onSecondScreenDisplay: (Int) -> Unit, val onLaunchAndroidApp: (HomeApp.LaunchableApp, Int?) -> Unit, val onBackground: () -> Unit, + val onShareLogs: () -> Unit, val onStop: () -> Unit, val onClose: () -> Unit, ) @@ -296,6 +297,11 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha Spacer(Modifier.width(10.dp)) Text(a.title ?: if (a.steam) "Steam" else "Desktop", fontSize = 17.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f)) + ShareSessionLogsButton(modifier = focus.track(page, "share-logs")) { + host.open = null + a.onShareLogs() + } + Spacer(Modifier.width(8.dp)) StopSessionButton(modifier = focus.track(page, "stop")) { host.open = null; confirmStop = true } } if (a.onSteamMenu != null && a.onQam != null) { @@ -610,3 +616,24 @@ private fun StopSessionButton(modifier: Modifier = Modifier, onClick: () -> Unit .controllerConfirm(onClick = onClick), ) { Text("×", fontSize = 25.sp, fontWeight = FontWeight.Medium, color = colors.error) } } + +@Composable +private fun ShareSessionLogsButton(modifier: Modifier = Modifier, onClick: () -> Unit) { + val colors = MaterialTheme.colorScheme + val pal = LocalPalette.current + val src = remember { MutableInteractionSource() } + val hot = src.collectIsFocusedAsState().value || src.collectIsHoveredAsState().value + Box( + contentAlignment = Alignment.Center, + modifier = modifier.widthIn(min = 88.dp).height(42.dp) + .semantics { contentDescription = "Send current session logs" } + .clip(RoundedCornerShape(12.dp)) + .background(if (hot) pal.signal.copy(alpha = 0.16f) else Color.Transparent) + .border(1.dp, if (hot) pal.signal else colors.outline.copy(alpha = 0.5f), RoundedCornerShape(12.dp)) + .hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, onClick = onClick) + .controllerConfirm(onClick = onClick), + ) { + Text("send logs", fontSize = 12.sp, fontWeight = FontWeight.SemiBold, + color = if (hot) pal.signal else colors.onSurfaceVariant, maxLines = 1) + } +}