diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-22 12:27:34 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-22 12:27:34 +0200 |
| commit | 899b1d9812c61a33554b67e3026850684e686ce1 (patch) | |
| tree | 415abc780daa5bd4cbb69d65bdb82157b44cf3ab | |
| parent | 48b44a6e27b87ee8bf5340a8d7ff37afaf04b7f9 (diff) | |
| download | mygram-droid-899b1d9812c61a33554b67e3026850684e686ce1.tar.gz mygram-droid-899b1d9812c61a33554b67e3026850684e686ce1.zip | |
Splash gates the app while Foldergram indexes
Foldergram is unusably slow during a scan, so the splash (renamed from
LockActivity) now shows on every start and return to the app: PIN if
set, then it polls /api/scan-progress every 5 s and stays on "Library
is indexing" until the scan ends. Home connects only after the splash.
Builds and deploys; the indexing path is not yet tested on the TV.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | app/src/main/AndroidManifest.xml | 2 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/App.java | 8 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/LockActivity.java | 63 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/MainActivity.java | 14 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/SplashActivity.java | 135 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-10-22-mygram-droid-design.md | 18 |
6 files changed, 164 insertions, 76 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 39f2033..1ccbd7e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,6 @@ <activity android:name=".SettingsActivity" /> <activity android:name=".FolderActivity" /> <activity android:name=".ViewerActivity" /> - <activity android:name=".LockActivity" /> + <activity android:name=".SplashActivity" /> </application> </manifest> diff --git a/app/src/main/java/mygram/droid/App.java b/app/src/main/java/mygram/droid/App.java index 1919343..a6fb9c8 100644 --- a/app/src/main/java/mygram/droid/App.java +++ b/app/src/main/java/mygram/droid/App.java @@ -24,7 +24,7 @@ import java.net.CookieManager; import java.net.CookiePolicy; public final class App extends Application implements Application.ActivityLifecycleCallbacks { - /** Until the PIN is entered; set again whenever the app leaves the screen. */ + /** Until the splash lets the user in; set again whenever the app leaves the screen. */ static boolean locked = true; private int started; @@ -38,12 +38,12 @@ public final class App extends Application implements Application.ActivityLifecy CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); } - /** Before any screen draws, cover it with the PIN prompt if the app is locked. */ + /** Before any screen draws, cover it with the splash (PIN, indexing wait) until it lets the user in. */ @Override public void onActivityStarted(Activity a) { started++; - if (locked && Prefs.hasPin() && !(a instanceof LockActivity)) - a.startActivity(new Intent(a, LockActivity.class)); + if (locked && !(a instanceof SplashActivity)) + a.startActivity(new Intent(a, SplashActivity.class)); } @Override diff --git a/app/src/main/java/mygram/droid/LockActivity.java b/app/src/main/java/mygram/droid/LockActivity.java deleted file mode 100644 index dd95915..0000000 --- a/app/src/main/java/mygram/droid/LockActivity.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * mygram-droid: Android TV client for Foldergram. - * Copyright (C) 2026 Danilo M. <danix@danix.xyz> - * - * 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.text.InputType; -import android.view.View; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Toast; - -/** Splash with the logo and a PIN prompt; App shows it while the app is locked. */ -public final class LockActivity extends Activity { - @Override - protected void onCreate(Bundle state) { - super.onCreate(state); - LinearLayout 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); - 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())) { - App.locked = false; - finish(); - } 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()); - setContentView(box); - pin.requestFocus(); - } - - /** 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(); - } -} diff --git a/app/src/main/java/mygram/droid/MainActivity.java b/app/src/main/java/mygram/droid/MainActivity.java index 877f180..7c7f194 100644 --- a/app/src/main/java/mygram/droid/MainActivity.java +++ b/app/src/main/java/mygram/droid/MainActivity.java @@ -59,7 +59,7 @@ public final class MainActivity extends Activity { private RecyclerView homeList; private HomeRows homeRows; private int tab = HOME; - private boolean ready, resumed, homeLoading; + private boolean ready, resumed, homeLoading, connecting; private LinearLayout homeButton, likedButton, foldersButton; @Override @@ -90,7 +90,7 @@ public final class MainActivity extends Activity { column.addView(content, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1)); root.addView(column, FILL); setContentView(root); - connect(); + // connect() runs from onResume once the splash has let the user in. } /** [logo] [Reels] [Liked] [Folders] [Settings]; the current view is marked. */ @@ -114,13 +114,17 @@ public final class MainActivity extends Activity { } private void connect() { + if (connecting) + return; message("Connecting…"); + connecting = true; Api.async(() -> { if (!Prefs.password().isEmpty()) Api.login(Prefs.server(), Prefs.password()); Likes.load(); return Api.randomFeed(); }, posts -> { + connecting = false; if (isDestroyed()) return; ready = true; @@ -129,6 +133,7 @@ public final class MainActivity extends Activity { rotateBackground(); show(tab); }, e -> { + connecting = false; if (!isDestroyed()) failed(e); }); @@ -205,8 +210,11 @@ public final class MainActivity extends Activity { protected void onResume() { super.onResume(); resumed = true; - if (!ready) + if (!ready) { + if (!connecting && !App.locked) + connect(); // also retries after an error, e.g. back from Settings return; + } handler.removeCallbacks(rotate); handler.postDelayed(rotate, Prefs.backgroundSeconds() * 1000L); if (tab == LIKED) diff --git a/app/src/main/java/mygram/droid/SplashActivity.java b/app/src/main/java/mygram/droid/SplashActivity.java new file mode 100644 index 0000000..ed94488 --- /dev/null +++ b/app/src/main/java/mygram/droid/SplashActivity.java @@ -0,0 +1,135 @@ +/* + * mygram-droid: Android TV client for Foldergram. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * + * 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); + } +} diff --git a/docs/superpowers/specs/2026-10-22-mygram-droid-design.md b/docs/superpowers/specs/2026-10-22-mygram-droid-design.md index 4545884..13dd378 100644 --- a/docs/superpowers/specs/2026-10-22-mygram-droid-design.md +++ b/docs/superpowers/specs/2026-10-22-mygram-droid-design.md @@ -142,11 +142,19 @@ videos only, endless, paged with the same seed. - Optional app PIN, 4 to 8 digits, typed twice; Remove PIN when one is set. - Header with a back arrow, like the folder grid. -### PIN lock - -- Only when a PIN is set: a splash with the logo and a PIN field covers - any screen at app start and whenever the app comes back after leaving - the screen (an Application lifecycle counter tracks started activities). +### Splash (PIN lock and indexing wait) + +- A splash with the logo covers any screen at app start and whenever the + app comes back after leaving the screen (an Application lifecycle + counter tracks started activities). Home connects only after it. +- When a PIN is set it asks for it first. +- Then it checks `GET /api/scan-progress`: while `isScanning`, it stays + with a spinner, "Library is indexing" and the server's phase message + plus the derivative job counter, polling every 5 s; Foldergram is + unusably slow while indexing. Idle: it closes at once. +- Unreachable at the first check: it lets the user in so Home shows its + error with Retry. After a scan was seen, failed polls keep waiting. +- A scan that starts while the app is open does not interrupt it. - Back on the lock screen leaves the app. - Stored as salt plus SHA-256, never the PIN. It is a privacy screen, not protection against someone with adb or root on the TV; clearing the |
