aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-22 12:27:34 +0200
committerDanilo M. <danix@danix.xyz>2026-10-22 12:27:34 +0200
commit899b1d9812c61a33554b67e3026850684e686ce1 (patch)
tree415abc780daa5bd4cbb69d65bdb82157b44cf3ab
parent48b44a6e27b87ee8bf5340a8d7ff37afaf04b7f9 (diff)
downloadmygram-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.xml2
-rw-r--r--app/src/main/java/mygram/droid/App.java8
-rw-r--r--app/src/main/java/mygram/droid/LockActivity.java63
-rw-r--r--app/src/main/java/mygram/droid/MainActivity.java14
-rw-r--r--app/src/main/java/mygram/droid/SplashActivity.java135
-rw-r--r--docs/superpowers/specs/2026-10-22-mygram-droid-design.md18
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