Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Native companion app for the desktop's **Connect Phone** (Remote Access) feature
- **Terminal space and zoom:** pinch with two fingers to magnify the whole terminal up to 400%, and pan with two fingers (or sideways with one) to read a part of it; one finger still scrolls the history. Swiping between tasks pauses while zoomed; double-tap or the zoom pill resets it. The expand icon in the title bar hides the title, tabs, and quick keys while keeping the message field and Send/Stop available; the restore icon or Android Back returns to the normal layout without losing your draft. Terminal and chat replies use full-width multiline fields, with action buttons below.
- **History:** opening a terminal loads the desktop's history (up to 10,000 lines), and the phone keeps up to 20,000 lines while the terminal stays open.
- **Changes:** the task's diff against its base branch, file by file with added and removed lines.
- **Quick replies and voice:** saved replies above the built-in chat's reply box (edit them in Settings; the terminal leaves them out to make room) and a mic button that dictates with Android's speech recognizer.
- **Quick replies and voice:** saved replies above the built-in chat's reply box and behind the star button in the terminal's reply box (edit them in Settings), and a mic button that dictates with Android's speech recognizer.
- **Widget:** a home-screen widget with the agents that need you and the usage meters, updated while the app is connected. Settings → Widget sets its background transparency (opaque, 75%, 50% or 25%; the border fades with the card, so your wallpaper shows through) and its card color (Obsidian, Slate or Light, each with text colors that stay readable).
- **Notes:** read a task's notes panel; edit and save it once paired.
- **New task:** pick a project, agent and model, then describe the work; needs pairing.
Expand Down
48 changes: 48 additions & 0 deletions android/app/src/main/java/com/parallelcode/phone/ComposerExtras.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,29 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Star
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AssistChip
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
Expand Down Expand Up @@ -54,6 +61,47 @@ fun QuickReplies(replies: List<String>, enabled: Boolean, onPick: (String) -> Un
}
}

/**
* The same saved replies behind one button, for a reply box with no row to spare: tapping the
* button lists them, and picking one hands it to [onPick]. Hidden when there are none. [modifier]
* applies to the button itself.
*/
@Composable
fun QuickRepliesButton(
replies: List<String>,
enabled: Boolean,
modifier: Modifier = Modifier,
onPick: (String) -> Unit,
) {
if (replies.isEmpty()) return
var open by remember { mutableStateOf(false) }
val haptic = LocalHapticFeedback.current
Box {
OutlinedButton(
onClick = { open = true },
enabled = enabled,
modifier = modifier,
shape = MaterialTheme.shapes.large,
) {
Icon(Icons.Filled.Star, contentDescription = "Quick replies")
}
DropdownMenu(expanded = open && enabled, onDismissRequest = { open = false }) {
replies.forEach { reply ->
DropdownMenuItem(
text = { Text(reply, maxLines = 2, overflow = TextOverflow.Ellipsis) },
onClick = {
// The closing menu still takes taps while it animates out: pick only once.
if (!open) return@DropdownMenuItem
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
open = false
onPick(reply)
},
)
}
}
}
}

/** Adds [text] to a draft, as typing it after what is already there would. */
fun appendToDraft(draft: String, text: String): String = when {
draft.isBlank() -> text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ private fun PhoneApp(model: PhoneViewModel) {
client = model.client,
alwaysFollowOutput = alwaysFollowOutput,
fitTerminalToPhone = fitTerminalToPhone && active,
quickReplies = quickReplies,
sendQuickReplies = sendQuickReplies,
promptHistory = model.promptHistory,
pageLabel = pageLabel,
nextNeedingYou = nextTaskNeedingYou(agents, agentId),
Expand Down
21 changes: 21 additions & 0 deletions android/app/src/main/java/com/parallelcode/phone/Screens.kt
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ fun AgentScreen(
client: RemoteClient,
alwaysFollowOutput: Boolean,
fitTerminalToPhone: Boolean,
quickReplies: List<String> = emptyList(),
sendQuickReplies: Boolean = false,
promptHistory: PromptHistoryStore? = null,
pageLabel: String? = null,
nextNeedingYou: RemoteAgent? = null,
Expand Down Expand Up @@ -1222,6 +1224,8 @@ fun AgentScreen(
ReplyBox(
agentId = agentId,
compact = terminalExpanded,
quickReplies = quickReplies,
sendQuickReplies = sendQuickReplies,
working = agent?.running == true &&
(agent.attention == "active" || agent.attention == "shell_busy"),
promptHistory = promptHistory,
Expand Down Expand Up @@ -1700,6 +1704,8 @@ private fun arrowKeyFor(label: String): Pair<ImageVector, String>? = when (label
private fun ReplyBox(
agentId: String,
compact: Boolean = false,
quickReplies: List<String> = emptyList(),
sendQuickReplies: Boolean = false,
working: Boolean = false,
promptHistory: PromptHistoryStore? = null,
needsInput: Boolean = false,
Expand Down Expand Up @@ -1821,6 +1827,21 @@ private fun ReplyBox(
enabled = !busy,
modifier = Modifier.fillMaxHeight(),
) { setDraft(appendToDraft(draft, it)) }
// Behind a button rather than a row of chips, so the terminal keeps its room.
QuickRepliesButton(
quickReplies,
enabled = !busy,
modifier = Modifier.fillMaxHeight(),
Comment on lines +1831 to +1834

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent terminal actions from clipping on narrow phones

On narrow displays or with enlarged accessibility text, this unscrollable action row now contains three icon-only OutlinedButtons (voice, quick replies, and history) plus Send/Stop. Material buttons' default minimum widths and horizontal padding can exhaust the available width, leaving the trailing Send button or its label clipped and unusable. Use a compact icon-button treatment, allow the actions to wrap/scroll, or otherwise constrain their widths.

Useful? React with 👍 / 👎.

) {
// A quick reply is for the agent, sent straight away or not: one that starts the
// draft leaves shell mode, so Send cannot run it as a shell command.
if (sendQuickReplies) {
run { send(it, false) }
} else {
if (draft.isBlank()) shellMode = false
setDraft(appendToDraft(draft, it))
}
}
if (promptHistory != null) {
OutlinedButton(
onClick = { showHistory = true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ fun SettingsScreen(
HorizontalDivider(thickness = 1.dp, color = AppTheme.extra.borderSubtle, modifier = Modifier.padding(vertical = 14.dp))
SettingSwitchRow(
title = "Send quick replies immediately",
description = "Tapping a quick reply in a built-in chat sends it straight away. Off, it is added to your draft first.",
description = "Tapping a quick reply sends it straight to the agent. Off, it is added to your draft first.",
checked = sendQuickReplies,
onCheckedChange = onSendQuickRepliesChange,
)
Expand Down Expand Up @@ -1076,7 +1076,7 @@ private fun QuickRepliesEditor(replies: List<String>, onChange: (List<String>) -
color = MaterialTheme.colorScheme.onSurface,
)
Text(
"Shown above the reply box in built-in chats; tap one to add it to your message. One per line.",
"Shown above the reply box in built-in chats and behind the star button in terminals. One per line.",
style = MaterialTheme.typography.bodyMedium,
color = AppTheme.extra.textMuted,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class SettingsStore(private val prefs: SharedPreferences) {
get() = prefs.getBoolean(KEY_FIT_TERMINAL, false)
set(value) = prefs.edit { putBoolean(KEY_FIT_TERMINAL, value) }

/** Replies offered above the reply box, one per line. */
/** Replies offered with the reply box (above it in chats, behind a button in terminals), one per line. */
var quickReplies: List<String>
get() = (prefs.getString(KEY_QUICK_REPLIES, null) ?: DEFAULT_QUICK_REPLIES.joinToString("\n"))
.lines().map { it.trim() }.filter { it.isNotEmpty() }
Expand Down
Loading