Polish session controls and server management
This commit is contained in:
@@ -44,6 +44,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -56,7 +57,9 @@ import java.io.InputStream;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -107,6 +110,10 @@ public final class MainActivity extends Activity {
|
|||||||
private static final String PAGE_UPDATE = "Update";
|
private static final String PAGE_UPDATE = "Update";
|
||||||
private static final String PAGE_ABOUT = "About";
|
private static final String PAGE_ABOUT = "About";
|
||||||
private static final String TERMINAL_ENTER = "\r";
|
private static final String TERMINAL_ENTER = "\r";
|
||||||
|
private static final String SERVER_INSTALL_COMMANDS = "chmod +x tmux-ui.run\n"
|
||||||
|
+ "./tmux-ui.run install\n"
|
||||||
|
+ "./tmux-ui.run service-install\n"
|
||||||
|
+ "./tmux-ui.run service-status";
|
||||||
private static final String[] SERVER_PROFILES = {
|
private static final String[] SERVER_PROFILES = {
|
||||||
"http://100.89.0.116:3000",
|
"http://100.89.0.116:3000",
|
||||||
"http://100.89.0.2:3000",
|
"http://100.89.0.2:3000",
|
||||||
@@ -116,6 +123,7 @@ public final class MainActivity extends Activity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
private final ExecutorService serverProbeExecutor = Executors.newFixedThreadPool(5);
|
||||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private UpdateManager updateManager;
|
private UpdateManager updateManager;
|
||||||
@@ -154,6 +162,9 @@ public final class MainActivity extends Activity {
|
|||||||
private String pendingImageUploadSession;
|
private String pendingImageUploadSession;
|
||||||
private int lastSessionCount = -1;
|
private int lastSessionCount = -1;
|
||||||
private int lastActiveSessionCount = -1;
|
private int lastActiveSessionCount = -1;
|
||||||
|
private final Map<String, Integer> serverSessionCounts = new HashMap<>();
|
||||||
|
private final Map<String, Integer> serverActiveSessionCounts = new HashMap<>();
|
||||||
|
private final Set<String> serverCountFailures = new HashSet<>();
|
||||||
private TerminalScreenBuffer terminalScreen = new TerminalScreenBuffer(DEFAULT_TERMINAL_COLS, DEFAULT_TERMINAL_ROWS);
|
private TerminalScreenBuffer terminalScreen = new TerminalScreenBuffer(DEFAULT_TERMINAL_COLS, DEFAULT_TERMINAL_ROWS);
|
||||||
private final StringBuilder queuedTerminalInput = new StringBuilder();
|
private final StringBuilder queuedTerminalInput = new StringBuilder();
|
||||||
private final Set<String> terminalExpandedBlocks = new HashSet<>();
|
private final Set<String> terminalExpandedBlocks = new HashSet<>();
|
||||||
@@ -167,6 +178,10 @@ public final class MainActivity extends Activity {
|
|||||||
private int terminalViewMode = TERMINAL_VIEW_CHAT;
|
private int terminalViewMode = TERMINAL_VIEW_CHAT;
|
||||||
private int terminalKeyPage;
|
private int terminalKeyPage;
|
||||||
private int terminalHistoryOffset;
|
private int terminalHistoryOffset;
|
||||||
|
private float terminalFontSizeSp = 11f;
|
||||||
|
private float terminalLineHeight = 1.05f;
|
||||||
|
private String terminalFontFamily = "monospace";
|
||||||
|
private String terminalThemeId = "dark";
|
||||||
private float terminalTouchStartY;
|
private float terminalTouchStartY;
|
||||||
private int terminalTouchStartScrollY;
|
private int terminalTouchStartScrollY;
|
||||||
private int terminalReconnectAttempt;
|
private int terminalReconnectAttempt;
|
||||||
@@ -295,6 +310,7 @@ public final class MainActivity extends Activity {
|
|||||||
1
|
1
|
||||||
));
|
));
|
||||||
setStatus("Servers");
|
setStatus("Servers");
|
||||||
|
refreshServerSummaries(serverUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private View serverProfileCard(String url) {
|
private View serverProfileCard(String url) {
|
||||||
@@ -351,15 +367,21 @@ public final class MainActivity extends Activity {
|
|||||||
stateBlock.setOrientation(LinearLayout.VERTICAL);
|
stateBlock.setOrientation(LinearLayout.VERTICAL);
|
||||||
stateBlock.setGravity(Gravity.END);
|
stateBlock.setGravity(Gravity.END);
|
||||||
TextView active = new TextView(this);
|
TextView active = new TextView(this);
|
||||||
active.setText(selected && lastActiveSessionCount >= 0 ? String.valueOf(lastActiveSessionCount) : "0");
|
Integer activeCount = serverActiveSessionCounts.get(url);
|
||||||
|
Integer totalCount = serverSessionCounts.get(url);
|
||||||
|
active.setText(activeCount == null ? "…" : String.valueOf(activeCount));
|
||||||
active.setTextColor(COLOR_ACCENT);
|
active.setTextColor(COLOR_ACCENT);
|
||||||
active.setTextSize(13);
|
active.setTextSize(13);
|
||||||
active.setTypeface(Typeface.MONOSPACE);
|
active.setTypeface(Typeface.MONOSPACE);
|
||||||
|
active.setTag("server-active:" + url);
|
||||||
TextView total = new TextView(this);
|
TextView total = new TextView(this);
|
||||||
total.setText("of " + (selected && lastSessionCount >= 0 ? lastSessionCount : 0));
|
total.setText(totalCount == null
|
||||||
|
? (serverCountFailures.contains(url) ? "offline" : "sessions")
|
||||||
|
: "of " + totalCount);
|
||||||
total.setTextColor(COLOR_TEXT_DIM);
|
total.setTextColor(COLOR_TEXT_DIM);
|
||||||
total.setTextSize(9);
|
total.setTextSize(9);
|
||||||
total.setTypeface(Typeface.MONOSPACE);
|
total.setTypeface(Typeface.MONOSPACE);
|
||||||
|
total.setTag("server-total:" + url);
|
||||||
stateBlock.addView(active);
|
stateBlock.addView(active);
|
||||||
stateBlock.addView(total);
|
stateBlock.addView(total);
|
||||||
card.addView(stateBlock);
|
card.addView(stateBlock);
|
||||||
@@ -378,6 +400,51 @@ public final class MainActivity extends Activity {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshServerSummaries(List<String> urls) {
|
||||||
|
for (String url : urls) {
|
||||||
|
serverProbeExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
List<SessionSummary> sessions = new SessionApiClient(url).getSessions();
|
||||||
|
int activeCount = 0;
|
||||||
|
for (SessionSummary session : sessions) {
|
||||||
|
String status = defaultValue(session.status, "").toLowerCase(java.util.Locale.ROOT);
|
||||||
|
if (status.contains("attach") || status.contains("active")) {
|
||||||
|
activeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int finalActiveCount = activeCount;
|
||||||
|
runOnUiThread(() -> updateServerSummary(url, sessions.size(), finalActiveCount, false));
|
||||||
|
} catch (Exception error) {
|
||||||
|
runOnUiThread(() -> updateServerSummary(url, 0, 0, true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateServerSummary(String url, int totalCount, int activeCount, boolean failed) {
|
||||||
|
if (failed) {
|
||||||
|
serverSessionCounts.remove(url);
|
||||||
|
serverActiveSessionCounts.remove(url);
|
||||||
|
serverCountFailures.add(url);
|
||||||
|
} else {
|
||||||
|
serverSessionCounts.put(url, totalCount);
|
||||||
|
serverActiveSessionCounts.put(url, activeCount);
|
||||||
|
serverCountFailures.remove(url);
|
||||||
|
}
|
||||||
|
if (!PAGE_SERVERS.equals(activeMainPage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextView active = root.findViewWithTag("server-active:" + url);
|
||||||
|
TextView total = root.findViewWithTag("server-total:" + url);
|
||||||
|
if (active != null) {
|
||||||
|
active.setText(failed ? "–" : String.valueOf(activeCount));
|
||||||
|
active.setTextColor(failed ? COLOR_TEXT_DIM : COLOR_ACCENT);
|
||||||
|
}
|
||||||
|
if (total != null) {
|
||||||
|
total.setText(failed ? "offline" : "of " + totalCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void renderSessionScreen() {
|
private void renderSessionScreen() {
|
||||||
closeTerminalSocket();
|
closeTerminalSocket();
|
||||||
activeSessionName = null;
|
activeSessionName = null;
|
||||||
@@ -510,6 +577,9 @@ public final class MainActivity extends Activity {
|
|||||||
LinearLayout utilities = new LinearLayout(this);
|
LinearLayout utilities = new LinearLayout(this);
|
||||||
utilities.setOrientation(LinearLayout.HORIZONTAL);
|
utilities.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
utilities.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
|
utilities.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
|
||||||
|
Button display = terminalToolButton("⚙", view -> promptSessionSettings(""));
|
||||||
|
display.setContentDescription("Session display settings");
|
||||||
|
utilities.addView(display);
|
||||||
Button refresh = compactButton("↻ Refresh", view -> refreshSessions());
|
Button refresh = compactButton("↻ Refresh", view -> refreshSessions());
|
||||||
refresh.setContentDescription("Refresh sessions");
|
refresh.setContentDescription("Refresh sessions");
|
||||||
utilities.addView(refresh);
|
utilities.addView(refresh);
|
||||||
@@ -846,24 +916,26 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
ScrollView scroll = new ScrollView(this);
|
ScrollView scroll = new ScrollView(this);
|
||||||
LinearLayout content = pageContent();
|
LinearLayout content = pageContent();
|
||||||
content.addView(infoBlock(
|
content.addView(createPageHeader(
|
||||||
"Installed",
|
"release channel",
|
||||||
appIdentityText() + "\n"
|
"Updates",
|
||||||
+ "Update source: " + updateSourceHost() + "\n"
|
"Check",
|
||||||
+ prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL)
|
view -> updateManager.checkSelected(true)
|
||||||
));
|
));
|
||||||
content.addView(sectionTitle("Update"));
|
content.addView(updateSummaryPanel());
|
||||||
|
content.addView(sectionTitle("Release source"));
|
||||||
content.addView(actionPanel(
|
content.addView(actionPanel(
|
||||||
actionButton("Auto check", view -> updateManager.check(true)),
|
|
||||||
actionButton("Gitea", view -> updateManager.checkGitea(true)),
|
actionButton("Gitea", view -> updateManager.checkGitea(true)),
|
||||||
actionButton("GitHub", view -> updateManager.checkGithub(true)),
|
actionButton("GitHub", view -> updateManager.checkGithub(true)),
|
||||||
actionButton("Preview", view -> updateManager.checkPreview(true)),
|
actionButton("Preview", view -> updateManager.checkPreview(true)),
|
||||||
actionButton("Selected", view -> updateManager.checkSelected(true)),
|
actionButton("Selected", view -> updateManager.checkSelected(true)),
|
||||||
actionButton("Source", view -> showUpdateSourcePicker()),
|
actionButton("Choose source", view -> showUpdateSourcePicker()),
|
||||||
actionButton("APK", view -> updateManager.openApkDownload())
|
actionButton("Download APK", view -> updateManager.openApkDownload())
|
||||||
));
|
));
|
||||||
content.addView(sectionTitle("Permissions"));
|
content.addView(sectionTitle("Server installation"));
|
||||||
content.addView(infoBlock("Android", permissionSummary()));
|
content.addView(serverInstallPanel());
|
||||||
|
content.addView(sectionTitle("Android access"));
|
||||||
|
content.addView(infoBlock("Permissions", permissionSummary()));
|
||||||
content.addView(actionPanel(
|
content.addView(actionPanel(
|
||||||
actionButton("Install permission", view -> openInstallPermissionSettings()),
|
actionButton("Install permission", view -> openInstallPermissionSettings()),
|
||||||
actionButton("Notifications", view -> requestNotificationPermission()),
|
actionButton("Notifications", view -> requestNotificationPermission()),
|
||||||
@@ -883,6 +955,91 @@ public final class MainActivity extends Activity {
|
|||||||
setStatus("Update and permissions");
|
setStatus("Update and permissions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private View updateSummaryPanel() {
|
||||||
|
LinearLayout panel = new LinearLayout(this);
|
||||||
|
panel.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
panel.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
panel.setPadding(dp(14), dp(12), dp(12), dp(12));
|
||||||
|
panel.setBackground(cardBackground());
|
||||||
|
|
||||||
|
TextView icon = new TextView(this);
|
||||||
|
icon.setText("⇩");
|
||||||
|
icon.setTextColor(COLOR_ACCENT);
|
||||||
|
icon.setTextSize(24);
|
||||||
|
icon.setGravity(Gravity.CENTER);
|
||||||
|
icon.setBackground(rounded(COLOR_ACCENT_DARK, 8, Color.TRANSPARENT, 0));
|
||||||
|
panel.addView(icon, new LinearLayout.LayoutParams(dp(44), dp(44)));
|
||||||
|
|
||||||
|
LinearLayout details = new LinearLayout(this);
|
||||||
|
details.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
details.setPadding(dp(12), 0, dp(8), 0);
|
||||||
|
TextView version = new TextView(this);
|
||||||
|
version.setText("v" + BuildConfig.VERSION_NAME + " · build " + BuildConfig.VERSION_CODE);
|
||||||
|
version.setTextColor(COLOR_TEXT);
|
||||||
|
version.setTextSize(14);
|
||||||
|
version.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||||
|
TextView source = bodyText(updateSourceHost());
|
||||||
|
source.setTextSize(10);
|
||||||
|
source.setSingleLine(true);
|
||||||
|
source.setEllipsize(TextUtils.TruncateAt.MIDDLE);
|
||||||
|
details.addView(version, matchWrap());
|
||||||
|
details.addView(source, matchWrap());
|
||||||
|
panel.addView(details, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
|
||||||
|
|
||||||
|
Button release = terminalToolButton("↗", view -> updateManager.openReleasePage());
|
||||||
|
release.setContentDescription("Open release page");
|
||||||
|
panel.addView(release);
|
||||||
|
LinearLayout.LayoutParams params = matchWrap();
|
||||||
|
params.bottomMargin = dp(8);
|
||||||
|
panel.setLayoutParams(params);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View serverInstallPanel() {
|
||||||
|
LinearLayout panel = new LinearLayout(this);
|
||||||
|
panel.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
panel.setPadding(dp(12), dp(11), dp(12), dp(12));
|
||||||
|
panel.setBackground(panelBackground());
|
||||||
|
|
||||||
|
TextView title = new TextView(this);
|
||||||
|
title.setText("tmux-ui server");
|
||||||
|
title.setTextColor(COLOR_TEXT);
|
||||||
|
title.setTextSize(15);
|
||||||
|
title.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
|
panel.addView(title, matchWrap());
|
||||||
|
TextView requirements = bodyText(
|
||||||
|
"Place tmux-ui.run on the target host\nNode.js 20+ · npm · tmux · Tailscale"
|
||||||
|
);
|
||||||
|
requirements.setTextSize(10);
|
||||||
|
requirements.setPadding(0, dp(3), 0, dp(9));
|
||||||
|
panel.addView(requirements, matchWrap());
|
||||||
|
|
||||||
|
TextView command = new TextView(this);
|
||||||
|
command.setText(SERVER_INSTALL_COMMANDS);
|
||||||
|
command.setTextColor(COLOR_TEXT);
|
||||||
|
command.setTextSize(11);
|
||||||
|
command.setTypeface(Typeface.MONOSPACE);
|
||||||
|
command.setTextIsSelectable(true);
|
||||||
|
command.setPadding(dp(10), dp(9), dp(10), dp(9));
|
||||||
|
command.setBackground(rounded(COLOR_FIELD, 7, COLOR_BORDER_SOFT, 1));
|
||||||
|
panel.addView(command, matchWrap());
|
||||||
|
|
||||||
|
LinearLayout actions = new LinearLayout(this);
|
||||||
|
actions.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
Button copy = compactButton("Copy commands", view -> copyText("Server install", SERVER_INSTALL_COMMANDS));
|
||||||
|
actions.addView(copy, new LinearLayout.LayoutParams(0, dp(40), 1));
|
||||||
|
Button add = compactButton("Add server", view -> promptCustomServer());
|
||||||
|
LinearLayout.LayoutParams addParams = new LinearLayout.LayoutParams(0, dp(40), 1);
|
||||||
|
addParams.leftMargin = dp(7);
|
||||||
|
actions.addView(add, addParams);
|
||||||
|
panel.addView(actions, spacedMatchWrap(10, 0));
|
||||||
|
|
||||||
|
LinearLayout.LayoutParams params = matchWrap();
|
||||||
|
params.bottomMargin = dp(8);
|
||||||
|
panel.setLayoutParams(params);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
private void renderAboutScreen() {
|
private void renderAboutScreen() {
|
||||||
closeTerminalSocket();
|
closeTerminalSocket();
|
||||||
activeSessionName = null;
|
activeSessionName = null;
|
||||||
@@ -1692,6 +1849,7 @@ public final class MainActivity extends Activity {
|
|||||||
private void openTerminal(String sessionName) {
|
private void openTerminal(String sessionName) {
|
||||||
closeTerminalSocket();
|
closeTerminalSocket();
|
||||||
activeSessionName = sessionName;
|
activeSessionName = sessionName;
|
||||||
|
loadTerminalDisplaySettings(sessionName);
|
||||||
terminalViewMode = TERMINAL_VIEW_CHAT;
|
terminalViewMode = TERMINAL_VIEW_CHAT;
|
||||||
projectList = null;
|
projectList = null;
|
||||||
sessionGroupList = null;
|
sessionGroupList = null;
|
||||||
@@ -1727,18 +1885,18 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
terminalScroll = new ScrollView(this);
|
terminalScroll = new ScrollView(this);
|
||||||
terminalScroll.setFillViewport(true);
|
terminalScroll.setFillViewport(true);
|
||||||
terminalScroll.setBackgroundColor(COLOR_TERMINAL_BG);
|
terminalScroll.setBackgroundColor(terminalBackgroundColor());
|
||||||
terminalText = new TextView(this);
|
terminalText = new TextView(this);
|
||||||
terminalText.setTextColor(COLOR_TEXT);
|
terminalText.setTextColor(COLOR_TEXT);
|
||||||
terminalText.setTextSize(11);
|
terminalText.setTextSize(terminalFontSizeSp);
|
||||||
terminalText.setTypeface(Typeface.MONOSPACE);
|
terminalText.setTypeface(terminalTypeface());
|
||||||
terminalText.setIncludeFontPadding(false);
|
terminalText.setIncludeFontPadding(false);
|
||||||
terminalText.setHorizontallyScrolling(true);
|
terminalText.setHorizontallyScrolling(true);
|
||||||
terminalText.setLineSpacing(0, 1.05f);
|
terminalText.setLineSpacing(0, terminalLineHeight);
|
||||||
terminalText.setGravity(Gravity.BOTTOM | Gravity.START);
|
terminalText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||||
terminalText.setTextIsSelectable(terminalSelectionEnabled);
|
terminalText.setTextIsSelectable(terminalSelectionEnabled);
|
||||||
terminalText.setPadding(dp(2), dp(8), dp(2), 0);
|
terminalText.setPadding(dp(2), dp(8), dp(2), 0);
|
||||||
terminalText.setBackgroundColor(COLOR_TERMINAL_BG);
|
terminalText.setBackgroundColor(terminalBackgroundColor());
|
||||||
terminalChatList = new LinearLayout(this);
|
terminalChatList = new LinearLayout(this);
|
||||||
terminalChatList.setOrientation(LinearLayout.VERTICAL);
|
terminalChatList.setOrientation(LinearLayout.VERTICAL);
|
||||||
terminalChatList.setPadding(0, dp(12), 0, 0);
|
terminalChatList.setPadding(0, dp(12), 0, 0);
|
||||||
@@ -2067,8 +2225,8 @@ public final class MainActivity extends Activity {
|
|||||||
TextView content = conversationContent(message, false);
|
TextView content = conversationContent(message, false);
|
||||||
content.setText((user ? "› " : "• ") + defaultValue(message.content, "(empty)"));
|
content.setText((user ? "› " : "• ") + defaultValue(message.content, "(empty)"));
|
||||||
content.setTextColor(user ? COLOR_ACCENT : COLOR_TEXT);
|
content.setTextColor(user ? COLOR_ACCENT : COLOR_TEXT);
|
||||||
content.setTextSize(12);
|
content.setTextSize(terminalFontSizeSp);
|
||||||
content.setTypeface(Typeface.MONOSPACE, user ? Typeface.BOLD : Typeface.NORMAL);
|
content.setTypeface(terminalTypeface(), user ? Typeface.BOLD : Typeface.NORMAL);
|
||||||
content.setPadding(0, 0, 0, 0);
|
content.setPadding(0, 0, 0, 0);
|
||||||
block.addView(content, matchWrap());
|
block.addView(content, matchWrap());
|
||||||
|
|
||||||
@@ -2143,8 +2301,8 @@ public final class MainActivity extends Activity {
|
|||||||
TextView content = new TextView(this);
|
TextView content = new TextView(this);
|
||||||
content.setText(message.content.isEmpty() ? "(empty)" : message.content);
|
content.setText(message.content.isEmpty() ? "(empty)" : message.content);
|
||||||
content.setTextColor(COLOR_TEXT);
|
content.setTextColor(COLOR_TEXT);
|
||||||
content.setTextSize(tool ? 11 : 14);
|
content.setTextSize(terminalFontSizeSp);
|
||||||
content.setTypeface(tool ? Typeface.MONOSPACE : Typeface.DEFAULT);
|
content.setTypeface(terminalTypeface());
|
||||||
content.setTextIsSelectable(true);
|
content.setTextIsSelectable(true);
|
||||||
content.setLineSpacing(dp(2), 1f);
|
content.setLineSpacing(dp(2), 1f);
|
||||||
content.setPadding(0, dp(6), 0, 0);
|
content.setPadding(0, dp(6), 0, 0);
|
||||||
@@ -2193,13 +2351,13 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
terminalLiveOutputText = new TextView(this);
|
terminalLiveOutputText = new TextView(this);
|
||||||
terminalLiveOutputText.setTextColor(COLOR_TEXT_MUTED);
|
terminalLiveOutputText.setTextColor(COLOR_TEXT_MUTED);
|
||||||
terminalLiveOutputText.setTextSize(11);
|
terminalLiveOutputText.setTextSize(terminalFontSizeSp);
|
||||||
terminalLiveOutputText.setTypeface(Typeface.MONOSPACE);
|
terminalLiveOutputText.setTypeface(terminalTypeface());
|
||||||
terminalLiveOutputText.setIncludeFontPadding(false);
|
terminalLiveOutputText.setIncludeFontPadding(false);
|
||||||
terminalLiveOutputText.setHorizontallyScrolling(false);
|
terminalLiveOutputText.setHorizontallyScrolling(false);
|
||||||
terminalLiveOutputText.setLineSpacing(0, 1.05f);
|
terminalLiveOutputText.setLineSpacing(0, terminalLineHeight);
|
||||||
terminalLiveOutputText.setGravity(Gravity.BOTTOM | Gravity.START);
|
terminalLiveOutputText.setGravity(Gravity.BOTTOM | Gravity.START);
|
||||||
terminalLiveOutputText.setBackgroundColor(COLOR_TERMINAL_BG);
|
terminalLiveOutputText.setBackgroundColor(terminalBackgroundColor());
|
||||||
terminalLiveOutputText.setPadding(0, dp(5), 0, 0);
|
terminalLiveOutputText.setPadding(0, dp(5), 0, 0);
|
||||||
terminalLiveOutputText.setMovementMethod(LinkMovementMethod.getInstance());
|
terminalLiveOutputText.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
terminalLiveOutputText.setHighlightColor(Color.TRANSPARENT);
|
terminalLiveOutputText.setHighlightColor(Color.TRANSPARENT);
|
||||||
@@ -2392,152 +2550,71 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showSessionActions(String sessionName) {
|
private void showSessionActions(String sessionName) {
|
||||||
String[] items = {
|
Dialog dialog = new Dialog(this);
|
||||||
"Status",
|
LinearLayout sheet = bottomSheetContent(dialog, sessionName, "Session actions");
|
||||||
"Rename",
|
addSheetActionSection(dialog, sheet, "OPEN & MANAGE",
|
||||||
"Send command",
|
new SheetAction("›_", "Open", () -> openTerminal(sessionName)),
|
||||||
"Split horizontal",
|
new SheetAction("◎", "Status", () -> showRaw("Session status", () -> api.sessionStatus(sessionName))),
|
||||||
"Split vertical",
|
new SheetAction("✎", "Rename", () -> promptRenameSession(sessionName)),
|
||||||
"Select pane",
|
new SheetAction("$", "Command", () -> promptSendCommand(sessionName)),
|
||||||
"Kill pane",
|
new SheetAction("Aa", "Display", () -> promptSessionSettings(sessionName)),
|
||||||
"Pin session",
|
new SheetAction("▰", "Project", () -> promptAddKanbanSession(sessionName))
|
||||||
"Unpin session",
|
);
|
||||||
"Mute session",
|
addSheetActionSection(dialog, sheet, "PANES",
|
||||||
"Unmute session",
|
new SheetAction("↔", "Split H", () -> runApiAction("Split horizontal", () -> api.splitPane(sessionName, "horizontal"))),
|
||||||
"Session settings",
|
new SheetAction("↕", "Split V", () -> runApiAction("Split vertical", () -> api.splitPane(sessionName, "vertical"))),
|
||||||
"Add to kanban project",
|
new SheetAction("◫", "Select", () -> promptPaneId("Select pane", paneId -> api.selectPane(sessionName, paneId))),
|
||||||
"Post hook event"
|
new SheetAction("×", "Kill pane", () -> promptPaneId("Kill pane", paneId -> api.killPane(sessionName, paneId)), true)
|
||||||
};
|
);
|
||||||
new AlertDialog.Builder(this)
|
addSheetActionSection(dialog, sheet, "PREFERENCES",
|
||||||
.setTitle(sessionName)
|
new SheetAction("★", "Pin", () -> runApiAction("Pin session", () -> api.setPinned(sessionName, true))),
|
||||||
.setItems(items, (dialog, which) -> {
|
new SheetAction("☆", "Unpin", () -> runApiAction("Unpin session", () -> api.setPinned(sessionName, false))),
|
||||||
switch (which) {
|
new SheetAction("◉", "Mute", () -> runApiAction("Mute session", () -> api.setMuted(sessionName, true))),
|
||||||
case 0:
|
new SheetAction("○", "Unmute", () -> runApiAction("Unmute session", () -> api.setMuted(sessionName, false))),
|
||||||
showRaw("Session status", () -> api.sessionStatus(sessionName));
|
new SheetAction("⚑", "Hook", () -> promptPostHookEvent(sessionName))
|
||||||
break;
|
);
|
||||||
case 1:
|
showBottomSheet(dialog, sheet, null);
|
||||||
promptRenameSession(sessionName);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
promptSendCommand(sessionName);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
runApiAction("Split horizontal", () -> api.splitPane(sessionName, "horizontal"));
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
runApiAction("Split vertical", () -> api.splitPane(sessionName, "vertical"));
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
promptPaneId("Select pane", paneId -> api.selectPane(sessionName, paneId));
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
promptPaneId("Kill pane", paneId -> api.killPane(sessionName, paneId));
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
runApiAction("Pin session", () -> api.setPinned(sessionName, true));
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
runApiAction("Unpin session", () -> api.setPinned(sessionName, false));
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
runApiAction("Mute session", () -> api.setMuted(sessionName, true));
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
runApiAction("Unmute session", () -> api.setMuted(sessionName, false));
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
promptSessionSettings(sessionName);
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
promptAddKanbanSession(sessionName);
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
promptPostHookEvent(sessionName);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showTerminalActions(String sessionName) {
|
private void showTerminalActions(String sessionName) {
|
||||||
String[] items = {
|
Dialog dialog = new Dialog(this);
|
||||||
terminalViewMode == TERMINAL_VIEW_CHAT ? "Open raw terminal" : "Open content view",
|
LinearLayout sheet = bottomSheetContent(dialog, sessionName, "Terminal controls");
|
||||||
"Reconnect",
|
addSheetActionSection(dialog, sheet, "VIEW",
|
||||||
"Clear local view and tmux history",
|
new SheetAction("◫", terminalViewMode == TERMINAL_VIEW_CHAT ? "Raw" : "Content", () ->
|
||||||
"Split horizontal",
|
showTerminalView(terminalViewMode == TERMINAL_VIEW_CHAT
|
||||||
"Split vertical",
|
? TERMINAL_VIEW_FULL : TERMINAL_VIEW_CHAT, true)),
|
||||||
"Zoom active pane",
|
new SheetAction("↻", "Reconnect", () -> connectTerminal(sessionName)),
|
||||||
"Page up",
|
new SheetAction("◎", "Status", () -> showRaw("Session status", () -> api.sessionStatus(sessionName))),
|
||||||
"Page down",
|
new SheetAction("Aa", "Display", () -> promptSessionSettings(sessionName)),
|
||||||
"Session status",
|
new SheetAction("↑", "Older", () -> scrollTerminalHistory(-terminalRows)),
|
||||||
"Send command",
|
new SheetAction("↓", "Newer", () -> scrollTerminalHistory(terminalRows))
|
||||||
"Tmux prefix",
|
);
|
||||||
"Detach tmux client",
|
addSheetActionSection(dialog, sheet, "LAYOUT",
|
||||||
"New tmux window",
|
new SheetAction("↔", "Split H", () -> runApiAction("Split horizontal", () -> api.splitPane(sessionName, "horizontal"))),
|
||||||
"Next tmux window",
|
new SheetAction("↕", "Split V", () -> runApiAction("Split vertical", () -> api.splitPane(sessionName, "vertical"))),
|
||||||
"Previous tmux window"
|
new SheetAction("▣", "Zoom", () -> sendTerminalInput("\u0002z")),
|
||||||
};
|
new SheetAction("⌫", "Clear", this::clearTerminalHistory, true)
|
||||||
new AlertDialog.Builder(this)
|
);
|
||||||
.setTitle(sessionName)
|
addSheetActionSection(dialog, sheet, "TMUX",
|
||||||
.setItems(items, (dialog, which) -> {
|
new SheetAction("C-b", "Prefix", () -> sendTerminalInput("\u0002")),
|
||||||
switch (which) {
|
new SheetAction("⇥", "Detach", () -> sendTerminalInput("\u0002d")),
|
||||||
case 0:
|
new SheetAction("+", "New win", () -> sendTerminalInput("\u0002c")),
|
||||||
showTerminalView(terminalViewMode == TERMINAL_VIEW_CHAT
|
new SheetAction("→", "Next win", () -> sendTerminalInput("\u0002n")),
|
||||||
? TERMINAL_VIEW_FULL : TERMINAL_VIEW_CHAT, true);
|
new SheetAction("←", "Prev win", () -> sendTerminalInput("\u0002p")),
|
||||||
break;
|
new SheetAction("$", "Command", () -> promptSendCommand(sessionName))
|
||||||
case 1:
|
);
|
||||||
connectTerminal(sessionName);
|
showBottomSheet(dialog, sheet, null);
|
||||||
break;
|
}
|
||||||
case 2:
|
|
||||||
terminalScreen.clear();
|
private void clearTerminalHistory() {
|
||||||
terminalText.setText("");
|
terminalScreen.clear();
|
||||||
if (terminalSocket != null) {
|
if (terminalText != null) {
|
||||||
terminalSocket.clearHistory();
|
terminalText.setText("");
|
||||||
}
|
}
|
||||||
break;
|
if (terminalSocket != null) {
|
||||||
case 3:
|
terminalSocket.clearHistory();
|
||||||
runApiAction("Split horizontal", () -> api.splitPane(sessionName, "horizontal"));
|
}
|
||||||
break;
|
setStatus("Terminal history cleared");
|
||||||
case 4:
|
|
||||||
runApiAction("Split vertical", () -> api.splitPane(sessionName, "vertical"));
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
sendTerminalInput("\u0002z");
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
scrollTerminalHistory(-terminalRows);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
scrollTerminalHistory(terminalRows);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
showRaw("Session status", () -> api.sessionStatus(sessionName));
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
promptSendCommand(sessionName);
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
sendTerminalInput("\u0002");
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
sendTerminalInput("\u0002d");
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
sendTerminalInput("\u0002c");
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
sendTerminalInput("\u0002n");
|
|
||||||
break;
|
|
||||||
case 14:
|
|
||||||
sendTerminalInput("\u0002p");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinearLayout createComposerBar() {
|
private LinearLayout createComposerBar() {
|
||||||
@@ -2809,25 +2886,249 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void promptSessionSettings(String sessionName) {
|
private void promptSessionSettings(String sessionName) {
|
||||||
LinearLayout form = formRoot();
|
String key = sessionName.isEmpty() ? "default" : sessionName;
|
||||||
EditText fontSize = formField(form, "Font size", "14");
|
float currentSize = prefs.getFloat("terminal_font_size:" + key,
|
||||||
EditText fontFamily = formField(form, "Font family", "monospace");
|
prefs.getFloat("terminal_font_size:default", 11f));
|
||||||
EditText lineHeight = formField(form, "Line height", "1.25");
|
float currentLineHeight = prefs.getFloat("terminal_line_height:" + key,
|
||||||
EditText themeId = formField(form, "Theme ID", "dark");
|
prefs.getFloat("terminal_line_height:default", 1.05f));
|
||||||
new AlertDialog.Builder(this)
|
String currentFamily = prefs.getString("terminal_font_family:" + key,
|
||||||
.setTitle("Session settings")
|
prefs.getString("terminal_font_family:default", "monospace"));
|
||||||
.setView(form)
|
String currentTheme = prefs.getString("terminal_theme:" + key,
|
||||||
.setNegativeButton("Cancel", null)
|
prefs.getString("terminal_theme:default", "dark"));
|
||||||
.setPositiveButton("Save", (dialog, which) -> runApiAction("Session settings", () ->
|
|
||||||
|
Dialog dialog = new Dialog(this);
|
||||||
|
LinearLayout sheet = bottomSheetContent(
|
||||||
|
dialog,
|
||||||
|
sessionName.isEmpty() ? "Session display" : sessionName,
|
||||||
|
sessionName.isEmpty() ? "Default terminal typography" : "Terminal typography for this session"
|
||||||
|
);
|
||||||
|
|
||||||
|
TextView sizeValue = settingValue("" + Math.round(currentSize) + " sp");
|
||||||
|
sheet.addView(settingHeader("FONT SIZE", sizeValue), matchWrap());
|
||||||
|
SeekBar size = new SeekBar(this);
|
||||||
|
size.setMin(9);
|
||||||
|
size.setMax(20);
|
||||||
|
size.setProgress(Math.round(currentSize));
|
||||||
|
size.setOnSeekBarChangeListener(seekListener(value -> sizeValue.setText(value + " sp")));
|
||||||
|
sheet.addView(size, matchWrap());
|
||||||
|
|
||||||
|
TextView familyLabel = formLabel("FONT");
|
||||||
|
familyLabel.setPadding(0, dp(10), 0, dp(6));
|
||||||
|
sheet.addView(familyLabel, matchWrap());
|
||||||
|
String[] selectedFamily = {currentFamily};
|
||||||
|
LinearLayout familyRow = new LinearLayout(this);
|
||||||
|
familyRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
Button mono = settingChoice("Mono", "monospace", selectedFamily);
|
||||||
|
Button sans = settingChoice("Sans", "sans", selectedFamily);
|
||||||
|
Button serif = settingChoice("Serif", "serif", selectedFamily);
|
||||||
|
Button[] familyButtons = {mono, sans, serif};
|
||||||
|
String[] familyValues = {"monospace", "sans", "serif"};
|
||||||
|
for (int index = 0; index < familyButtons.length; index++) {
|
||||||
|
Button button = familyButtons[index];
|
||||||
|
String value = familyValues[index];
|
||||||
|
button.setOnClickListener(view -> {
|
||||||
|
selectedFamily[0] = value;
|
||||||
|
for (int item = 0; item < familyButtons.length; item++) {
|
||||||
|
styleSettingChoice(familyButtons[item], familyValues[item].equals(selectedFamily[0]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
styleSettingChoice(button, value.equals(selectedFamily[0]));
|
||||||
|
LinearLayout.LayoutParams choiceParams = new LinearLayout.LayoutParams(0, dp(40), 1);
|
||||||
|
if (index > 0) {
|
||||||
|
choiceParams.leftMargin = dp(6);
|
||||||
|
}
|
||||||
|
familyRow.addView(button, choiceParams);
|
||||||
|
}
|
||||||
|
sheet.addView(familyRow, matchWrap());
|
||||||
|
|
||||||
|
TextView themeLabel = formLabel("THEME");
|
||||||
|
themeLabel.setPadding(0, dp(10), 0, dp(6));
|
||||||
|
sheet.addView(themeLabel, matchWrap());
|
||||||
|
String[] selectedTheme = {currentTheme};
|
||||||
|
LinearLayout themeRow = new LinearLayout(this);
|
||||||
|
themeRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
Button dark = settingChoice("Dark", "dark", selectedTheme);
|
||||||
|
Button oled = settingChoice("OLED", "oled", selectedTheme);
|
||||||
|
Button[] themeButtons = {dark, oled};
|
||||||
|
String[] themeValues = {"dark", "oled"};
|
||||||
|
for (int index = 0; index < themeButtons.length; index++) {
|
||||||
|
Button button = themeButtons[index];
|
||||||
|
String value = themeValues[index];
|
||||||
|
button.setOnClickListener(view -> {
|
||||||
|
selectedTheme[0] = value;
|
||||||
|
for (int item = 0; item < themeButtons.length; item++) {
|
||||||
|
styleSettingChoice(themeButtons[item], themeValues[item].equals(selectedTheme[0]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
styleSettingChoice(button, value.equals(selectedTheme[0]));
|
||||||
|
LinearLayout.LayoutParams choiceParams = new LinearLayout.LayoutParams(0, dp(40), 1);
|
||||||
|
if (index > 0) {
|
||||||
|
choiceParams.leftMargin = dp(6);
|
||||||
|
}
|
||||||
|
themeRow.addView(button, choiceParams);
|
||||||
|
}
|
||||||
|
sheet.addView(themeRow, matchWrap());
|
||||||
|
|
||||||
|
TextView lineValue = settingValue(Math.round(currentLineHeight * 100f) + "%");
|
||||||
|
sheet.addView(settingHeader("LINE HEIGHT", lineValue), matchWrap());
|
||||||
|
SeekBar lineHeight = new SeekBar(this);
|
||||||
|
lineHeight.setMin(90);
|
||||||
|
lineHeight.setMax(160);
|
||||||
|
lineHeight.setProgress(Math.round(currentLineHeight * 100f));
|
||||||
|
lineHeight.setOnSeekBarChangeListener(seekListener(value -> lineValue.setText(value + "%")));
|
||||||
|
sheet.addView(lineHeight, matchWrap());
|
||||||
|
|
||||||
|
LinearLayout actions = new LinearLayout(this);
|
||||||
|
actions.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
Button reset = compactButton("Reset", view -> {
|
||||||
|
prefs.edit()
|
||||||
|
.remove("terminal_font_size:" + key)
|
||||||
|
.remove("terminal_font_family:" + key)
|
||||||
|
.remove("terminal_line_height:" + key)
|
||||||
|
.remove("terminal_theme:" + key)
|
||||||
|
.apply();
|
||||||
|
dialog.dismiss();
|
||||||
|
if (sessionName.equals(activeSessionName) || sessionName.isEmpty()) {
|
||||||
|
loadTerminalDisplaySettings(defaultValue(activeSessionName, ""));
|
||||||
|
applyTerminalDisplaySettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actions.addView(reset, new LinearLayout.LayoutParams(0, dp(44), 1));
|
||||||
|
Button save = primaryButton("Save", view -> {
|
||||||
|
float selectedSize = size.getProgress();
|
||||||
|
float selectedHeight = lineHeight.getProgress() / 100f;
|
||||||
|
prefs.edit()
|
||||||
|
.putFloat("terminal_font_size:" + key, selectedSize)
|
||||||
|
.putString("terminal_font_family:" + key, selectedFamily[0])
|
||||||
|
.putFloat("terminal_line_height:" + key, selectedHeight)
|
||||||
|
.putString("terminal_theme:" + key, selectedTheme[0])
|
||||||
|
.apply();
|
||||||
|
dialog.dismiss();
|
||||||
|
if (sessionName.equals(activeSessionName) || sessionName.isEmpty()) {
|
||||||
|
loadTerminalDisplaySettings(defaultValue(activeSessionName, ""));
|
||||||
|
applyTerminalDisplaySettings();
|
||||||
|
}
|
||||||
|
if (!sessionName.isEmpty()) {
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
api.updateSessionSettings(
|
api.updateSessionSettings(
|
||||||
sessionName,
|
sessionName,
|
||||||
Integer.parseInt(defaultValue(fontSize.getText().toString().trim(), "14")),
|
Math.round(selectedSize),
|
||||||
defaultValue(fontFamily.getText().toString().trim(), "monospace"),
|
selectedFamily[0],
|
||||||
Double.parseDouble(defaultValue(lineHeight.getText().toString().trim(), "1.25")),
|
selectedHeight,
|
||||||
defaultValue(themeId.getText().toString().trim(), "dark")
|
selectedTheme[0]
|
||||||
)
|
);
|
||||||
))
|
} catch (Exception error) {
|
||||||
.show();
|
runOnUiThread(() -> showMessage("Settings sync failed: " + error.getMessage()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
showMessage("Session display saved");
|
||||||
|
});
|
||||||
|
LinearLayout.LayoutParams saveParams = new LinearLayout.LayoutParams(0, dp(44), 1);
|
||||||
|
saveParams.leftMargin = dp(8);
|
||||||
|
actions.addView(save, saveParams);
|
||||||
|
sheet.addView(actions, spacedMatchWrap(16, 0));
|
||||||
|
showBottomSheet(dialog, sheet, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTerminalDisplaySettings(String sessionName) {
|
||||||
|
String key = sessionName == null || sessionName.isEmpty() ? "default" : sessionName;
|
||||||
|
terminalFontSizeSp = prefs.getFloat("terminal_font_size:" + key,
|
||||||
|
prefs.getFloat("terminal_font_size:default", 11f));
|
||||||
|
terminalLineHeight = prefs.getFloat("terminal_line_height:" + key,
|
||||||
|
prefs.getFloat("terminal_line_height:default", 1.05f));
|
||||||
|
terminalFontFamily = prefs.getString("terminal_font_family:" + key,
|
||||||
|
prefs.getString("terminal_font_family:default", "monospace"));
|
||||||
|
terminalThemeId = prefs.getString("terminal_theme:" + key,
|
||||||
|
prefs.getString("terminal_theme:default", "dark"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTerminalDisplaySettings() {
|
||||||
|
Typeface typeface = terminalTypeface();
|
||||||
|
if (terminalText != null) {
|
||||||
|
terminalText.setTextSize(terminalFontSizeSp);
|
||||||
|
terminalText.setTypeface(typeface);
|
||||||
|
terminalText.setLineSpacing(0, terminalLineHeight);
|
||||||
|
terminalText.setBackgroundColor(terminalBackgroundColor());
|
||||||
|
}
|
||||||
|
if (terminalLiveOutputText != null) {
|
||||||
|
terminalLiveOutputText.setTextSize(terminalFontSizeSp);
|
||||||
|
terminalLiveOutputText.setTypeface(typeface);
|
||||||
|
terminalLiveOutputText.setLineSpacing(0, terminalLineHeight);
|
||||||
|
terminalLiveOutputText.setBackgroundColor(terminalBackgroundColor());
|
||||||
|
}
|
||||||
|
if (terminalScroll != null) {
|
||||||
|
terminalScroll.setBackgroundColor(terminalBackgroundColor());
|
||||||
|
}
|
||||||
|
if (terminalChatList != null && terminalViewMode == TERMINAL_VIEW_CHAT) {
|
||||||
|
renderTerminalConversation();
|
||||||
|
}
|
||||||
|
resizeTerminalToViewport(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Typeface terminalTypeface() {
|
||||||
|
if ("sans".equals(terminalFontFamily)) {
|
||||||
|
return Typeface.SANS_SERIF;
|
||||||
|
}
|
||||||
|
if ("serif".equals(terminalFontFamily)) {
|
||||||
|
return Typeface.SERIF;
|
||||||
|
}
|
||||||
|
return Typeface.MONOSPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int terminalBackgroundColor() {
|
||||||
|
return "oled".equals(terminalThemeId) ? Color.BLACK : COLOR_TERMINAL_BG;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextView settingValue(String text) {
|
||||||
|
TextView value = new TextView(this);
|
||||||
|
value.setText(text);
|
||||||
|
value.setTextColor(COLOR_ACCENT);
|
||||||
|
value.setTextSize(11);
|
||||||
|
value.setTypeface(Typeface.MONOSPACE, Typeface.BOLD);
|
||||||
|
value.setGravity(Gravity.END);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinearLayout settingHeader(String label, TextView value) {
|
||||||
|
LinearLayout row = new LinearLayout(this);
|
||||||
|
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
row.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
row.setPadding(0, dp(12), 0, 0);
|
||||||
|
row.addView(formLabel(label), new LinearLayout.LayoutParams(0, dp(28), 1));
|
||||||
|
row.addView(value, new LinearLayout.LayoutParams(dp(80), dp(28)));
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeekBar.OnSeekBarChangeListener seekListener(IntValueListener listener) {
|
||||||
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
listener.onValue(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button settingChoice(String label, String value, String[] selected) {
|
||||||
|
Button button = toolbarButton(label, view -> selected[0] = value);
|
||||||
|
button.setTextSize(11);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void styleSettingChoice(Button button, boolean selected) {
|
||||||
|
button.setTextColor(selected ? Color.rgb(14, 38, 24) : COLOR_TEXT_MUTED);
|
||||||
|
button.setBackground(selected
|
||||||
|
? rounded(COLOR_ACCENT, 7, COLOR_ACCENT, 1)
|
||||||
|
: rounded(COLOR_CARD_ALT, 7, COLOR_BORDER, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void promptGroupMessages() {
|
private void promptGroupMessages() {
|
||||||
@@ -3336,6 +3637,46 @@ public final class MainActivity extends Activity {
|
|||||||
return sheet;
|
return sheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addSheetActionSection(
|
||||||
|
Dialog dialog,
|
||||||
|
LinearLayout sheet,
|
||||||
|
String title,
|
||||||
|
SheetAction... actions
|
||||||
|
) {
|
||||||
|
TextView label = formLabel(title);
|
||||||
|
label.setPadding(dp(2), dp(8), dp(2), dp(6));
|
||||||
|
sheet.addView(label, matchWrap());
|
||||||
|
for (int index = 0; index < actions.length; index += 3) {
|
||||||
|
LinearLayout row = new LinearLayout(this);
|
||||||
|
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
for (int column = 0; column < 3; column++) {
|
||||||
|
int actionIndex = index + column;
|
||||||
|
if (actionIndex >= actions.length) {
|
||||||
|
row.addView(new View(this), new LinearLayout.LayoutParams(0, dp(54), 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SheetAction action = actions[actionIndex];
|
||||||
|
Button button = toolbarButton(action.icon + "\n" + action.title, view -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
action.action.run();
|
||||||
|
});
|
||||||
|
button.setTextSize(action.icon.length() > 2 ? 9 : 11);
|
||||||
|
button.setGravity(Gravity.CENTER);
|
||||||
|
button.setPadding(dp(3), dp(3), dp(3), dp(3));
|
||||||
|
if (action.danger) {
|
||||||
|
button.setTextColor(COLOR_DANGER);
|
||||||
|
}
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, dp(54), 1);
|
||||||
|
if (column > 0) {
|
||||||
|
params.leftMargin = dp(6);
|
||||||
|
}
|
||||||
|
params.bottomMargin = dp(6);
|
||||||
|
row.addView(button, params);
|
||||||
|
}
|
||||||
|
sheet.addView(row, matchWrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private EditText sheetField(LinearLayout sheet, String labelText, String hint, String value) {
|
private EditText sheetField(LinearLayout sheet, String labelText, String hint, String value) {
|
||||||
TextView label = formLabel(labelText);
|
TextView label = formLabel(labelText);
|
||||||
label.setPadding(0, dp(10), 0, dp(6));
|
label.setPadding(0, dp(10), 0, dp(6));
|
||||||
@@ -3355,7 +3696,14 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showBottomSheet(Dialog dialog, LinearLayout sheet, EditText focus) {
|
private void showBottomSheet(Dialog dialog, LinearLayout sheet, EditText focus) {
|
||||||
dialog.setContentView(sheet);
|
ScrollView scroller = new ScrollView(this);
|
||||||
|
scroller.setFillViewport(false);
|
||||||
|
scroller.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS);
|
||||||
|
scroller.addView(sheet, new ScrollView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
dialog.setContentView(scroller);
|
||||||
dialog.setCanceledOnTouchOutside(true);
|
dialog.setCanceledOnTouchOutside(true);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
Window window = dialog.getWindow();
|
Window window = dialog.getWindow();
|
||||||
@@ -3696,6 +4044,16 @@ public final class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void copyText(String label, String text) {
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
if (clipboard == null) {
|
||||||
|
showMessage("Clipboard unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText(label, text));
|
||||||
|
showMessage(label + " copied");
|
||||||
|
}
|
||||||
|
|
||||||
private void insertComposerText(String text) {
|
private void insertComposerText(String text) {
|
||||||
if (inputField == null) {
|
if (inputField == null) {
|
||||||
return;
|
return;
|
||||||
@@ -4574,6 +4932,7 @@ public final class MainActivity extends Activity {
|
|||||||
eventSocket = null;
|
eventSocket = null;
|
||||||
}
|
}
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
|
serverProbeExecutor.shutdownNow();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4584,4 +4943,26 @@ public final class MainActivity extends Activity {
|
|||||||
private interface TextApiAction {
|
private interface TextApiAction {
|
||||||
void run(String text) throws Exception;
|
void run(String text) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface IntValueListener {
|
||||||
|
void onValue(int value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SheetAction {
|
||||||
|
final String icon;
|
||||||
|
final String title;
|
||||||
|
final Runnable action;
|
||||||
|
final boolean danger;
|
||||||
|
|
||||||
|
SheetAction(String icon, String title, Runnable action) {
|
||||||
|
this(icon, title, action, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SheetAction(String icon, String title, Runnable action, boolean danger) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.title = title;
|
||||||
|
this.action = action;
|
||||||
|
this.danger = danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user