/* * mygram-droid: Android TV client for Foldergram. * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ package mygram.droid; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.InputType; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.json.JSONObject; /** * Gate shown on every start and return to the app: the PIN prompt when a PIN * is set, then a wait while Foldergram indexes, which makes it unusably slow. */ public final class SplashActivity extends Activity { private static final long POLL_MS = 5000; private final Handler handler = new Handler(Looper.getMainLooper()); private final Runnable poll = this::checkScan; private LinearLayout box; private TextView status; @Override protected void onCreate(Bundle state) { super.onCreate(state); box = Ui.column(this); ImageView logo = new ImageView(this); logo.setImageResource(R.drawable.ic_launcher); logo.setContentDescription("mygram"); int s = Ui.dp(this, 96); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(s, s); lp.bottomMargin = Ui.dp(this, 24); box.addView(logo, lp); setContentView(box); if (Prefs.hasPin()) askPin(); else checkScan(); } private void askPin() { EditText pin = Ui.field(this, "PIN", InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); box.addView(pin); View ok = Ui.iconButton(this, R.drawable.ic_check, "Unlock", v -> { if (Prefs.checkPin(pin.getText().toString())) { box.removeViews(1, box.getChildCount() - 1); checkScan(); } else { pin.setText(""); Toast.makeText(this, "Wrong PIN", Toast.LENGTH_SHORT).show(); } }); box.addView(ok); // The keyboard's done key unlocks; the D-pad cannot leave the keyboard. pin.setOnEditorActionListener((v, action, event) -> ok.performClick()); pin.requestFocus(); } /** Waits while the library is being indexed; lets the user in once it is idle. */ private void checkScan() { if (Prefs.server() == null) { // first run: setup comes next done(); return; } Api.async(() -> Api.call("GET", "/scan-progress"), o -> { if (isDestroyed()) return; if (!o.optBoolean("isScanning")) { done(); return; } showScan(o); handler.postDelayed(poll, POLL_MS); }, e -> { if (isDestroyed()) return; if (status == null) done(); // unreachable at the first check: Home reports it with Retry else handler.postDelayed(poll, POLL_MS); // mid-scan the server is slow; keep waiting }); } private void showScan(JSONObject o) { if (status == null) { box.addView(new ProgressBar(this)); box.addView(Ui.text(this, "Library is indexing", 24)); status = Ui.text(this, "", 16); box.addView(status); } String msg = Post.str(o, "currentPhaseMessage"); if (msg == null) msg = o.optString("phase"); int done = o.optInt("processedDerivativeJobs"), total = o.optInt("queuedDerivativeJobs"); status.setText(total > 0 ? msg + " " + done + " / " + total : msg); } private void done() { App.locked = false; finish(); } /** Back leaves the app instead of revealing the screen underneath. */ @Override @SuppressWarnings("deprecation") // no predictive back on this TV; onBackPressed still fires public void onBackPressed() { finishAffinity(); } @Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); } }