Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
974abed278 | ||
|
|
47af792d62 | ||
|
|
0c7dba0ef1 | ||
|
|
f1eb30cea0 |
@@ -29,6 +29,7 @@ import android.text.method.LinkMovementMethod;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
@@ -165,6 +166,9 @@ public final class MainActivity extends Activity {
|
||||
private boolean terminalFollowOutput = true;
|
||||
private int terminalViewMode = TERMINAL_VIEW_CHAT;
|
||||
private int terminalKeyPage;
|
||||
private int terminalHistoryOffset;
|
||||
private float terminalTouchStartY;
|
||||
private int terminalTouchStartScrollY;
|
||||
private int terminalReconnectAttempt;
|
||||
private int terminalConnectionGeneration;
|
||||
private int eventReconnectAttempt;
|
||||
@@ -1698,6 +1702,7 @@ public final class MainActivity extends Activity {
|
||||
terminalSelectionEnabled = false;
|
||||
terminalFollowOutput = true;
|
||||
terminalKeyPage = 0;
|
||||
terminalHistoryOffset = 0;
|
||||
terminalExpandedBlocks.clear();
|
||||
terminalExpandedMessages.clear();
|
||||
terminalConversationMessages.clear();
|
||||
@@ -1732,17 +1737,19 @@ public final class MainActivity extends Activity {
|
||||
terminalText.setLineSpacing(0, 1.05f);
|
||||
terminalText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||
terminalText.setTextIsSelectable(terminalSelectionEnabled);
|
||||
terminalText.setPadding(dp(2), dp(8), dp(2), dp(8));
|
||||
terminalText.setPadding(dp(2), dp(8), dp(2), 0);
|
||||
terminalText.setBackgroundColor(COLOR_TERMINAL_BG);
|
||||
terminalChatList = new LinearLayout(this);
|
||||
terminalChatList.setOrientation(LinearLayout.VERTICAL);
|
||||
terminalChatList.setPadding(dp(10), dp(12), dp(10), dp(18));
|
||||
terminalChatList.setPadding(0, dp(12), 0, 0);
|
||||
terminalChatList.addView(projectStateText("Loading conversation..."), matchWrap());
|
||||
showTerminalView(terminalViewMode, false);
|
||||
terminalScroll.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
|
||||
resizeTerminalToViewport(false));
|
||||
terminalScroll.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) ->
|
||||
terminalFollowOutput = !view.canScrollVertically(1));
|
||||
terminalScroll.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
|
||||
terminalScroll.setOnTouchListener(this::handleTerminalScrollTouch);
|
||||
root.addView(terminalScroll, new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
0,
|
||||
@@ -1764,6 +1771,8 @@ public final class MainActivity extends Activity {
|
||||
});
|
||||
inputField.post(() -> {
|
||||
inputField.requestFocus();
|
||||
inputField.setSelection(inputField.length());
|
||||
inputField.setCursorVisible(true);
|
||||
hideKeyboard();
|
||||
});
|
||||
}
|
||||
@@ -1862,19 +1871,21 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
terminalScroll.removeAllViews();
|
||||
if (mode == TERMINAL_VIEW_CHAT) {
|
||||
terminalText.setHorizontallyScrolling(false);
|
||||
terminalText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
terminalText.setHighlightColor(Color.TRANSPARENT);
|
||||
terminalScroll.addView(terminalChatList, new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
renderTerminalConversation();
|
||||
} else {
|
||||
terminalText.setHorizontallyScrolling(true);
|
||||
terminalText.setMovementMethod(null);
|
||||
terminalText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||
terminalScroll.addView(terminalText, new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
renderTerminalNow();
|
||||
}
|
||||
terminalText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||
terminalScroll.addView(terminalText, new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
renderTerminalNow();
|
||||
if (terminalAccessoryBar != null) {
|
||||
terminalAccessoryBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@@ -2049,124 +2060,74 @@ public final class MainActivity extends Activity {
|
||||
|
||||
private View conversationMessageRow(ConversationMessage message) {
|
||||
boolean user = "user".equals(message.role);
|
||||
boolean tool = message.isTool();
|
||||
LinearLayout row = new LinearLayout(this);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setGravity(user ? Gravity.END : Gravity.START);
|
||||
LinearLayout block = new LinearLayout(this);
|
||||
block.setOrientation(LinearLayout.VERTICAL);
|
||||
block.setPadding(dp(10), dp(5), dp(10), dp(7));
|
||||
|
||||
LinearLayout bubble = new LinearLayout(this);
|
||||
bubble.setOrientation(LinearLayout.VERTICAL);
|
||||
bubble.setPadding(user || tool ? dp(12) : dp(2), dp(9), user || tool ? dp(12) : dp(4), dp(10));
|
||||
bubble.setBackground(tool
|
||||
? rounded(COLOR_PANEL, 8, COLOR_BORDER, 1)
|
||||
: user
|
||||
? rounded(COLOR_ACCENT_DARK, 8, COLOR_ACCENT, 1)
|
||||
: rounded(Color.TRANSPARENT, 0, Color.TRANSPARENT, 0));
|
||||
TextView content = conversationContent(message, false);
|
||||
content.setText((user ? "› " : "• ") + defaultValue(message.content, "(empty)"));
|
||||
content.setTextColor(user ? COLOR_ACCENT : COLOR_TEXT);
|
||||
content.setTextSize(12);
|
||||
content.setTypeface(Typeface.MONOSPACE, user ? Typeface.BOLD : Typeface.NORMAL);
|
||||
content.setPadding(0, 0, 0, 0);
|
||||
block.addView(content, matchWrap());
|
||||
|
||||
TextView meta = new TextView(this);
|
||||
meta.setTextColor(user ? COLOR_ACCENT : tool ? COLOR_ACCENT_WARM : COLOR_TEXT_MUTED);
|
||||
meta.setTextSize(9);
|
||||
meta.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||
meta.setSingleLine(true);
|
||||
meta.setEllipsize(TextUtils.TruncateAt.END);
|
||||
meta.setText(tool ? conversationToolTitle(message) : conversationStatusPart(message).replaceFirst("^ · ", ""));
|
||||
if (tool || (!"complete".equals(message.status) && !message.status.isEmpty())) {
|
||||
bubble.addView(meta, matchWrap());
|
||||
if (!"complete".equals(message.status) && !message.status.isEmpty()) {
|
||||
TextView status = bodyText(message.status);
|
||||
status.setTextColor(message.status.toLowerCase(java.util.Locale.ROOT).contains("error")
|
||||
? COLOR_DANGER : COLOR_TEXT_DIM);
|
||||
status.setTextSize(9);
|
||||
status.setTypeface(Typeface.MONOSPACE);
|
||||
status.setPadding(dp(14), dp(3), 0, 0);
|
||||
block.addView(status, matchWrap());
|
||||
}
|
||||
|
||||
boolean expanded = terminalExpandedMessages.contains(message.messageId);
|
||||
if (!tool || expanded) {
|
||||
bubble.addView(conversationContent(message, tool), matchWrap());
|
||||
} else {
|
||||
TextView collapsed = bodyText("Tap to show " + message.content.length() + " characters");
|
||||
collapsed.setTextSize(10);
|
||||
collapsed.setPadding(0, dp(5), 0, 0);
|
||||
bubble.addView(collapsed, matchWrap());
|
||||
}
|
||||
if (tool) {
|
||||
bubble.setOnClickListener(view -> {
|
||||
if (!terminalExpandedMessages.add(message.messageId)) {
|
||||
terminalExpandedMessages.remove(message.messageId);
|
||||
}
|
||||
renderTerminalConversation();
|
||||
});
|
||||
}
|
||||
|
||||
if (!tool && !user) {
|
||||
row.addView(bubble, matchWrap());
|
||||
} else if (!tool) {
|
||||
LinearLayout.LayoutParams bubbleParams = new LinearLayout.LayoutParams(
|
||||
0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
0.82f
|
||||
);
|
||||
View spacer = new View(this);
|
||||
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
|
||||
0,
|
||||
1,
|
||||
0.18f
|
||||
);
|
||||
row.addView(spacer, spacerParams);
|
||||
row.addView(bubble, bubbleParams);
|
||||
} else {
|
||||
row.addView(bubble, matchWrap());
|
||||
}
|
||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
rowParams.bottomMargin = dp(9);
|
||||
row.setLayoutParams(rowParams);
|
||||
return row;
|
||||
params.bottomMargin = dp(5);
|
||||
block.setLayoutParams(params);
|
||||
return block;
|
||||
}
|
||||
|
||||
private View conversationToolGroup(List<ConversationMessage> tools) {
|
||||
LinearLayout group = new LinearLayout(this);
|
||||
group.setOrientation(LinearLayout.VERTICAL);
|
||||
String firstId = tools.isEmpty() ? "empty" : tools.get(0).messageId;
|
||||
String lastId = tools.isEmpty() ? "empty" : tools.get(tools.size() - 1).messageId;
|
||||
String groupKey = "tools:" + firstId + ":" + lastId;
|
||||
boolean expanded = terminalExpandedMessages.contains(groupKey);
|
||||
String firstTitle = tools.isEmpty() ? "Tool activity" : conversationToolTitle(tools.get(0));
|
||||
TextView summary = new TextView(this);
|
||||
summary.setText((expanded ? "▼ " : "▶ ") + tools.size() + " tool "
|
||||
+ (tools.size() == 1 ? "activity" : "activities") + " · " + compactLabel(firstTitle, 44));
|
||||
summary.setTextColor(COLOR_ACCENT_WARM);
|
||||
summary.setTextSize(10);
|
||||
summary.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||
summary.setPadding(dp(10), dp(6), dp(10), dp(6));
|
||||
summary.setOnClickListener(view -> {
|
||||
if (!terminalExpandedMessages.add(groupKey)) {
|
||||
terminalExpandedMessages.remove(groupKey);
|
||||
}
|
||||
renderTerminalConversation();
|
||||
});
|
||||
summary.setContentDescription((expanded ? "Collapse " : "Expand ") + tools.size() + " tool activities");
|
||||
group.addView(summary, matchWrap());
|
||||
|
||||
HorizontalScrollView scroller = new HorizontalScrollView(this);
|
||||
scroller.setHorizontalScrollBarEnabled(false);
|
||||
LinearLayout chips = new LinearLayout(this);
|
||||
chips.setOrientation(LinearLayout.HORIZONTAL);
|
||||
chips.setGravity(Gravity.CENTER_VERTICAL);
|
||||
for (ConversationMessage message : tools) {
|
||||
String label = compactLabel(conversationToolTitle(message), 18);
|
||||
Button chip = compactButton(label, view -> {
|
||||
if (!terminalExpandedMessages.add(message.messageId)) {
|
||||
terminalExpandedMessages.remove(message.messageId);
|
||||
}
|
||||
renderTerminalConversation();
|
||||
});
|
||||
chip.setTextSize(9);
|
||||
chip.setContentDescription("Toggle " + label);
|
||||
chips.addView(chip);
|
||||
}
|
||||
scroller.addView(chips, new HorizontalScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
dp(34)
|
||||
));
|
||||
group.addView(scroller, matchWrap());
|
||||
|
||||
for (ConversationMessage message : tools) {
|
||||
if (!terminalExpandedMessages.contains(message.messageId)) {
|
||||
continue;
|
||||
if (expanded) {
|
||||
for (ConversationMessage message : tools) {
|
||||
LinearLayout detail = new LinearLayout(this);
|
||||
detail.setOrientation(LinearLayout.VERTICAL);
|
||||
detail.setPadding(dp(18), dp(3), dp(10), dp(7));
|
||||
TextView title = bodyText(conversationToolTitle(message));
|
||||
title.setTextColor(COLOR_ACCENT_WARM);
|
||||
title.setTextSize(9);
|
||||
title.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||
detail.addView(title, matchWrap());
|
||||
detail.addView(conversationContent(message, true), matchWrap());
|
||||
group.addView(detail, matchWrap());
|
||||
}
|
||||
LinearLayout detail = new LinearLayout(this);
|
||||
detail.setOrientation(LinearLayout.VERTICAL);
|
||||
detail.setPadding(dp(10), dp(7), dp(10), dp(9));
|
||||
detail.setBackground(rounded(COLOR_FIELD, 7, COLOR_BORDER_SOFT, 1));
|
||||
TextView title = bodyText(conversationToolTitle(message));
|
||||
title.setTextColor(COLOR_ACCENT_WARM);
|
||||
title.setTextSize(9);
|
||||
title.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||
detail.addView(title, matchWrap());
|
||||
detail.addView(conversationContent(message, true), matchWrap());
|
||||
LinearLayout.LayoutParams detailParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
detailParams.topMargin = dp(5);
|
||||
group.addView(detail, detailParams);
|
||||
}
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
@@ -2203,43 +2164,49 @@ public final class MainActivity extends Activity {
|
||||
private View conversationLiveTerminalPanel() {
|
||||
LinearLayout panel = new LinearLayout(this);
|
||||
panel.setOrientation(LinearLayout.VERTICAL);
|
||||
panel.setPadding(dp(12), dp(9), dp(12), dp(10));
|
||||
panel.setBackground(rounded(COLOR_FIELD, 8, COLOR_BORDER, 1));
|
||||
panel.setOnClickListener(view -> showTerminalView(TERMINAL_VIEW_FULL, true));
|
||||
panel.setPadding(dp(2), dp(10), dp(2), 0);
|
||||
|
||||
LinearLayout heading = new LinearLayout(this);
|
||||
heading.setOrientation(LinearLayout.HORIZONTAL);
|
||||
heading.setGravity(Gravity.CENTER_VERTICAL);
|
||||
TextView title = new TextView(this);
|
||||
title.setText("Live terminal");
|
||||
title.setTextColor(COLOR_TEXT);
|
||||
title.setTextSize(11);
|
||||
title.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
heading.addView(title, new LinearLayout.LayoutParams(0, dp(22), 1));
|
||||
title.setText("LIVE TERMINAL");
|
||||
title.setTextColor(COLOR_TEXT_DIM);
|
||||
title.setTextSize(9);
|
||||
title.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||
heading.addView(title, new LinearLayout.LayoutParams(0, dp(32), 1));
|
||||
terminalLiveStatusText = new TextView(this);
|
||||
terminalLiveStatusText.setTextSize(9);
|
||||
terminalLiveStatusText.setTypeface(Typeface.MONOSPACE);
|
||||
terminalLiveStatusText.setSingleLine(true);
|
||||
heading.addView(terminalLiveStatusText, new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
dp(22)
|
||||
dp(32)
|
||||
));
|
||||
Button older = terminalToolButton("↑", view -> scrollTerminalHistory(-terminalRows));
|
||||
older.setContentDescription("Load older terminal history");
|
||||
heading.addView(older);
|
||||
Button newer = terminalToolButton("↓", view -> scrollTerminalHistory(terminalRows));
|
||||
newer.setContentDescription("Return toward live terminal output");
|
||||
heading.addView(newer);
|
||||
panel.addView(heading, matchWrap());
|
||||
|
||||
terminalLiveOutputText = new TextView(this);
|
||||
terminalLiveOutputText.setTextColor(COLOR_TEXT_MUTED);
|
||||
terminalLiveOutputText.setTextSize(10);
|
||||
terminalLiveOutputText.setTextSize(11);
|
||||
terminalLiveOutputText.setTypeface(Typeface.MONOSPACE);
|
||||
terminalLiveOutputText.setMaxLines(6);
|
||||
terminalLiveOutputText.setEllipsize(TextUtils.TruncateAt.END);
|
||||
terminalLiveOutputText.setIncludeFontPadding(false);
|
||||
terminalLiveOutputText.setHorizontallyScrolling(false);
|
||||
terminalLiveOutputText.setLineSpacing(0, 1.05f);
|
||||
terminalLiveOutputText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||
terminalLiveOutputText.setBackgroundColor(COLOR_TERMINAL_BG);
|
||||
terminalLiveOutputText.setPadding(0, dp(5), 0, 0);
|
||||
terminalLiveOutputText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
terminalLiveOutputText.setHighlightColor(Color.TRANSPARENT);
|
||||
terminalLiveOutputText.setTextIsSelectable(true);
|
||||
terminalLiveOutputText.addOnLayoutChangeListener((view, left, top, right, bottom,
|
||||
oldLeft, oldTop, oldRight, oldBottom) -> resizeTerminalToViewport(false));
|
||||
panel.addView(terminalLiveOutputText, matchWrap());
|
||||
|
||||
TextView hint = bodyText("Tap for full terminal");
|
||||
hint.setTextSize(9);
|
||||
hint.setTextColor(COLOR_TEXT_DIM);
|
||||
hint.setPadding(0, dp(6), 0, 0);
|
||||
panel.addView(hint, matchWrap());
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -2249,10 +2216,19 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
String socket = terminalSocketStatus.toLowerCase(java.util.Locale.ROOT);
|
||||
boolean connected = socket.contains("connected") && !socket.contains("disconnected");
|
||||
terminalLiveStatusText.setText(connected ? "● connected" : "● " + defaultValue(socket, "connecting"));
|
||||
terminalLiveStatusText.setTextColor(connected ? COLOR_SUCCESS
|
||||
terminalLiveStatusText.setText(terminalHistoryOffset > 0
|
||||
? "● " + terminalHistoryOffset + " back"
|
||||
: connected ? "● live" : "● " + defaultValue(socket, "connecting"));
|
||||
terminalLiveStatusText.setTextColor(terminalHistoryOffset > 0 ? COLOR_ACCENT_WARM : connected ? COLOR_SUCCESS
|
||||
: socket.contains("error") || socket.contains("disconnected") ? COLOR_DANGER : COLOR_ACCENT_WARM);
|
||||
terminalLiveOutputText.setText(terminalScreen.renderTail(6));
|
||||
terminalLiveOutputText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
terminalLiveOutputText.setHighlightColor(Color.TRANSPARENT);
|
||||
terminalLiveOutputText.setText(terminalScreen.renderFocused(terminalExpandedBlocks, key -> {
|
||||
if (!terminalExpandedBlocks.add(key)) {
|
||||
terminalExpandedBlocks.remove(key);
|
||||
}
|
||||
renderTerminalNow();
|
||||
}));
|
||||
}
|
||||
|
||||
private void updateTerminalMeta() {
|
||||
@@ -2531,14 +2507,10 @@ public final class MainActivity extends Activity {
|
||||
sendTerminalInput("\u0002z");
|
||||
break;
|
||||
case 6:
|
||||
if (terminalSocket != null) {
|
||||
terminalSocket.scroll(-terminalRows);
|
||||
}
|
||||
scrollTerminalHistory(-terminalRows);
|
||||
break;
|
||||
case 7:
|
||||
if (terminalSocket != null) {
|
||||
terminalSocket.scroll(terminalRows);
|
||||
}
|
||||
scrollTerminalHistory(terminalRows);
|
||||
break;
|
||||
case 8:
|
||||
showRaw("Session status", () -> api.sessionStatus(sessionName));
|
||||
@@ -3546,6 +3518,8 @@ public final class MainActivity extends Activity {
|
||||
terminalConnected = true;
|
||||
terminalConnecting = false;
|
||||
terminalReconnectAttempt = 0;
|
||||
terminalHistoryOffset = 0;
|
||||
terminalFollowOutput = true;
|
||||
terminalSocketStatus = "terminal connected";
|
||||
updateTerminalMeta();
|
||||
setStatus("Connected " + sessionName);
|
||||
@@ -3599,24 +3573,33 @@ public final class MainActivity extends Activity {
|
||||
if (terminalText == null || terminalScroll == null) {
|
||||
return;
|
||||
}
|
||||
int width = terminalScroll.getWidth();
|
||||
TextView viewportText = terminalViewMode == TERMINAL_VIEW_CHAT && terminalLiveOutputText != null
|
||||
? terminalLiveOutputText : terminalText;
|
||||
int width = viewportText.getWidth();
|
||||
if (width <= 0) {
|
||||
width = terminalScroll.getWidth();
|
||||
}
|
||||
int height = terminalScroll.getHeight();
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
int horizontalPadding = terminalText.getPaddingLeft() + terminalText.getPaddingRight();
|
||||
int verticalPadding = terminalText.getPaddingTop() + terminalText.getPaddingBottom();
|
||||
float charWidth = terminalText.getPaint().measureText("0000000000") / 10f;
|
||||
int horizontalPadding = viewportText.getPaddingLeft() + viewportText.getPaddingRight();
|
||||
int verticalPadding = viewportText.getPaddingTop() + viewportText.getPaddingBottom();
|
||||
float charWidth = viewportText.getPaint().measureText("0000000000") / 10f;
|
||||
if (charWidth <= 0f) {
|
||||
charWidth = dp(8);
|
||||
}
|
||||
int lineHeight = terminalText.getLineHeight();
|
||||
int lineHeight = viewportText.getLineHeight();
|
||||
if (lineHeight <= 0) {
|
||||
lineHeight = dp(16);
|
||||
}
|
||||
int usableWidth = Math.max(1, width - horizontalPadding);
|
||||
int cols = clamp((int) Math.floor(usableWidth / charWidth), MIN_TERMINAL_COLS, MAX_TERMINAL_COLS);
|
||||
int rows = clamp((height - verticalPadding) / lineHeight, MIN_TERMINAL_ROWS, MAX_TERMINAL_ROWS);
|
||||
if (terminalLiveOutputText != null) {
|
||||
terminalLiveOutputText.setMinHeight(rows * lineHeight);
|
||||
terminalLiveOutputText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||
}
|
||||
if (cols == terminalCols && rows == terminalRows && !forceSend) {
|
||||
return;
|
||||
}
|
||||
@@ -3659,6 +3642,11 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
TerminalSocketClient socket = terminalSocket;
|
||||
if (socket != null && !socket.isClosed() && terminalConnected) {
|
||||
if (terminalHistoryOffset > 0) {
|
||||
socket.scroll(terminalHistoryOffset);
|
||||
terminalHistoryOffset = 0;
|
||||
terminalFollowOutput = true;
|
||||
}
|
||||
socket.sendInput(data);
|
||||
setStatus("Sent input");
|
||||
return;
|
||||
@@ -3774,6 +3762,9 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
|
||||
private void scrollTerminalBottom() {
|
||||
if (terminalHistoryOffset > 0) {
|
||||
scrollTerminalHistory(terminalHistoryOffset);
|
||||
}
|
||||
terminalFollowOutput = true;
|
||||
if (terminalScroll != null) {
|
||||
terminalScroll.post(() -> terminalScroll.fullScroll(View.FOCUS_DOWN));
|
||||
@@ -3781,6 +3772,56 @@ public final class MainActivity extends Activity {
|
||||
setStatus("Following terminal output");
|
||||
}
|
||||
|
||||
private void scrollTerminalHistory(int lines) {
|
||||
TerminalSocketClient socket = terminalSocket;
|
||||
if (socket == null || socket.isClosed() || !terminalConnected || lines == 0) {
|
||||
setStatus("Terminal history unavailable while disconnected");
|
||||
return;
|
||||
}
|
||||
if (lines < 0) {
|
||||
terminalHistoryOffset += Math.abs(lines);
|
||||
terminalFollowOutput = false;
|
||||
} else {
|
||||
if (terminalHistoryOffset == 0) {
|
||||
setStatus("Live terminal output");
|
||||
return;
|
||||
}
|
||||
int amount = Math.min(lines, terminalHistoryOffset);
|
||||
terminalHistoryOffset = Math.max(0, terminalHistoryOffset - amount);
|
||||
terminalFollowOutput = terminalHistoryOffset == 0;
|
||||
lines = amount;
|
||||
}
|
||||
socket.scroll(lines);
|
||||
setStatus(terminalHistoryOffset == 0
|
||||
? "Live terminal output"
|
||||
: "Terminal history · " + terminalHistoryOffset + " lines back");
|
||||
}
|
||||
|
||||
private boolean handleTerminalScrollTouch(View view, MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
terminalTouchStartY = event.getY();
|
||||
terminalTouchStartScrollY = terminalScroll == null ? 0 : terminalScroll.getScrollY();
|
||||
return false;
|
||||
}
|
||||
if (event.getActionMasked() != MotionEvent.ACTION_UP || terminalScroll == null) {
|
||||
return false;
|
||||
}
|
||||
float distance = event.getY() - terminalTouchStartY;
|
||||
if (Math.abs(distance) < dp(48)) {
|
||||
return false;
|
||||
}
|
||||
int movedLocally = Math.abs(terminalScroll.getScrollY() - terminalTouchStartScrollY);
|
||||
int lineHeight = terminalLiveOutputText == null ? dp(16) : terminalLiveOutputText.getLineHeight();
|
||||
int lines = clamp(Math.round(Math.abs(distance) / Math.max(1, lineHeight)), 4, terminalRows);
|
||||
if (distance > 0 && !terminalScroll.canScrollVertically(-1) && movedLocally <= dp(24)) {
|
||||
terminalScroll.post(() -> scrollTerminalHistory(-lines));
|
||||
} else if (distance < 0 && terminalHistoryOffset > 0
|
||||
&& !terminalScroll.canScrollVertically(1) && movedLocally <= dp(24)) {
|
||||
terminalScroll.post(() -> scrollTerminalHistory(lines));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setTerminalKeyPage(int page) {
|
||||
terminalKeyPage = (page + 4) % 4;
|
||||
updateAccessoryTabs();
|
||||
@@ -3995,7 +4036,12 @@ public final class MainActivity extends Activity {
|
||||
input.setHintTextColor(COLOR_TEXT_DIM);
|
||||
input.setTextSize(13);
|
||||
input.setPadding(dp(8), dp(5), dp(8), dp(5));
|
||||
input.setBackground(inputBackground());
|
||||
input.setBackground(composerInputBackground());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
GradientDrawable cursor = rounded(COLOR_ACCENT, 1, Color.TRANSPARENT, 0);
|
||||
cursor.setSize(dp(2), dp(20));
|
||||
input.setTextCursorDrawable(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
private Button compactButton(String label, View.OnClickListener listener) {
|
||||
@@ -4028,6 +4074,14 @@ public final class MainActivity extends Activity {
|
||||
return rounded(COLOR_FIELD, 8, COLOR_BORDER, 1);
|
||||
}
|
||||
|
||||
private StateListDrawable composerInputBackground() {
|
||||
StateListDrawable states = new StateListDrawable();
|
||||
states.addState(new int[]{android.R.attr.state_focused},
|
||||
rounded(COLOR_CARD_ALT, 7, COLOR_ACCENT, 1));
|
||||
states.addState(new int[]{}, rounded(COLOR_CARD, 7, COLOR_BORDER, 1));
|
||||
return states;
|
||||
}
|
||||
|
||||
private GradientDrawable rounded(int color, int radiusDp, int strokeColor, int strokeDp) {
|
||||
GradientDrawable drawable = new GradientDrawable();
|
||||
drawable.setColor(color);
|
||||
|
||||
Reference in New Issue
Block a user