package com.dragontcp.client; import android.app.Activity; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; public class LogActivity extends Activity { private static final int BG = Color.rgb(11, 15, 20); private static final int CARD = Color.rgb(22, 28, 36); private static final int BORDER = Color.rgb(43, 53, 66); private static final int TEXT = Color.rgb(244, 247, 251); private static final int MUTED = Color.rgb(139, 151, 168); private static final int LOG_BG = Color.rgb(7, 10, 14); private static final int LOG_TEXT = Color.rgb(179, 194, 214); private TextView logText; private ScrollView logScroll; private final AppLog.Listener logListener = line -> runOnUiThread(() -> appendLogLine(line)); @Override public void onCreate(Bundle state) { super.onCreate(state); forceDarkSystemUi(); buildUi(); } @Override protected void onStart() { super.onStart(); logText.setText(AppLog.history()); AppLog.addListener(logListener); logScroll.post(this::scrollLogToBottom); } @Override protected void onStop() { AppLog.removeListener(logListener); super.onStop(); } private void forceDarkSystemUi() { Window w = getWindow(); w.setStatusBarColor(BG); w.setNavigationBarColor(Color.rgb(7, 10, 14)); if (Build.VERSION.SDK_INT >= 26) w.getDecorView().setSystemUiVisibility(0); } private void buildUi() { LinearLayout root = new LinearLayout(this); root.setOrientation(LinearLayout.VERTICAL); root.setBackgroundColor(BG); root.setPadding(dp(16), dp(14), dp(16), dp(12)); LinearLayout header = new LinearLayout(this); header.setOrientation(LinearLayout.HORIZONTAL); header.setGravity(Gravity.CENTER_VERTICAL); header.setPadding(dp(2), dp(2), dp(2), dp(12)); LinearLayout heading = new LinearLayout(this); heading.setOrientation(LinearLayout.VERTICAL); TextView title = text("Logs", 25, TEXT, true); TextView subtitle = text("DragonTCP Lite", 12, MUTED, false); subtitle.setPadding(0, dp(2), 0, 0); heading.addView(title); heading.addView(subtitle); header.addView(heading, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); Button back = smallButton("BACK"); Button clear = smallButton("CLEAR"); LinearLayout.LayoutParams bp = new LinearLayout.LayoutParams(dp(76), dp(42)); bp.setMarginEnd(dp(8)); header.addView(back, bp); header.addView(clear, new LinearLayout.LayoutParams(dp(82), dp(42))); root.addView(header); LinearLayout panel = new LinearLayout(this); panel.setOrientation(LinearLayout.VERTICAL); panel.setPadding(dp(10), dp(10), dp(10), dp(10)); panel.setBackground(roundRect(CARD, 16, BORDER, 1)); logScroll = new ScrollView(this); logScroll.setFillViewport(true); logScroll.setFocusable(false); logScroll.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); logScroll.setBackground(roundRect(LOG_BG, 11, Color.rgb(30, 38, 49), 1)); logText = new TextView(this); logText.setTextSize(12f); logText.setTextColor(LOG_TEXT); logText.setTypeface(Typeface.MONOSPACE); logText.setTextIsSelectable(true); logText.setPadding(dp(11), dp(10), dp(11), dp(10)); logText.setLineSpacing(0, 1.10f); logScroll.addView(logText, new ScrollView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT )); panel.addView(logScroll, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f )); root.addView(panel, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f )); back.setOnClickListener(v -> finish()); clear.setOnClickListener(v -> { AppLog.clear(); logText.setText(""); logScroll.scrollTo(0, 0); }); setContentView(root); applyInsets(root, dp(16), dp(14), dp(16), dp(12)); } private void appendLogLine(String line) { View child = logScroll.getChildAt(0); int contentHeight = child == null ? 0 : child.getHeight(); int maxScroll = Math.max(0, contentHeight - logScroll.getHeight()); boolean follow = maxScroll - logScroll.getScrollY() < dp(48); logText.append(line + "\n"); if (follow) logScroll.post(this::scrollLogToBottom); } private void scrollLogToBottom() { View child = logScroll.getChildAt(0); if (child == null) return; int y = Math.max(0, child.getHeight() - logScroll.getHeight()); logScroll.scrollTo(0, y); } private TextView text(String value, float size, int color, boolean bold) { TextView t = new TextView(this); t.setText(value); t.setTextSize(size); t.setTextColor(color); if (bold) t.setTypeface(Typeface.DEFAULT, Typeface.BOLD); return t; } private Button smallButton(String value) { Button b = new Button(this); b.setText(value); b.setAllCaps(false); b.setTextSize(11); b.setTypeface(Typeface.DEFAULT, Typeface.BOLD); b.setTextColor(TEXT); b.setBackground(roundRect(Color.rgb(32, 40, 51), 10, BORDER, 1)); return b; } private GradientDrawable roundRect(int color, int radiusDp, int strokeColor, int strokeDp) { GradientDrawable d = new GradientDrawable(); d.setColor(color); d.setCornerRadius(dp(radiusDp)); if (strokeDp > 0) d.setStroke(dp(strokeDp), strokeColor); return d; } private int dp(int value) { return Math.round(value * getResources().getDisplayMetrics().density); } /** * Pads the root by the system bar insets. Required from API 35, where the * activity is laid out edge to edge and would otherwise draw under the * status and navigation bars. */ private void applyInsets(final View root, final int l, final int t, final int r, final int b) { root.setOnApplyWindowInsetsListener((v, insets) -> { int top, bottom, left, right; if (Build.VERSION.SDK_INT >= 30) { android.graphics.Insets bars = insets.getInsets(WindowInsets.Type.systemBars()); top = bars.top; bottom = bars.bottom; left = bars.left; right = bars.right; } else { top = insets.getSystemWindowInsetTop(); bottom = insets.getSystemWindowInsetBottom(); left = insets.getSystemWindowInsetLeft(); right = insets.getSystemWindowInsetRight(); } v.setPadding(l + left, t + top, r + right, b + bottom); return insets; }); root.requestApplyInsets(); } }