diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java index c05077c2..4b4a1b2d 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java @@ -202,35 +202,28 @@ public static void show(Activity act, String endpoint, String title, int iconRes content.addView(row(ctx, R.drawable.ic_arrow_right, act.getString(R.string.k2go_sheet_open), Emphasis.ACCENT, null, false, v -> { dlg.dismiss(); openContent(act, endpoint); })); content.addView(about); - // K2GO-422: an installed forgejo with the example repos present -> offer "Update repos" - // here (the module action menu). Gated by the SAME status the detail uses (hasRepos), read - // off the main thread; the row is inserted after About when the box answers, if the sheet - // is still up. Tapping runs the shared refresh IN the sheet (it does NOT dismiss). - if ("forgejo".equals(key)) { + // K2GO-449: an installed content module's update action comes from the ModuleActions + // registry. A plain entry (code_addons, code_assets) shows its row now; a gated entry + // (forgejo "Update repos") checks its gate off the main thread and, if it passes and the + // sheet is still up, inserts the row after About. The module detail screen keeps Forgejo's + // richer install-repos / blocked flow. + final ModuleActions.InstalledAction ia = ModuleActions.installed(key); + final ModuleActions.Gate gate = ia == null ? null : ia.gate; + if (ia != null && gate == null) { + content.addView(row(ctx, ia.iconRes, act.getString(ia.labelRes), + Emphasis.ACCENT, null, false, v -> ia.handler.run(act, v))); + } else if (ia != null) { final LinearLayout host = content; org.appdevforall.k2go.util.AppExecutors.get().io().execute(() -> { - final org.appdevforall.k2go.forgejo.data.ForgejoStatusClient.Status st = - new org.appdevforall.k2go.forgejo.data.ForgejoStatusClient().fetch(); - // Gate on canUpdateRepos (manageable AND repos present): a null read (cannot tell), - // no repos, or an admin we cannot authenticate (blocked) shows no Update row. - if (st == null || !st.canUpdateRepos()) return; + if (!gate.canShow()) return; act.runOnUiThread(() -> { if (!dlg.isShowing()) return; - View urow = row(ctx, R.drawable.ic_refresh, - act.getString(R.string.k2go_forgejo_update_repos), Emphasis.ACCENT, null, false, - v -> org.appdevforall.k2go.forgejo.presentation.ForgejoRepoRefresh.start(act, v)); - host.addView(urow, host.indexOfChild(about) + 1); + View arow = row(ctx, ia.iconRes, act.getString(ia.labelRes), + Emphasis.ACCENT, null, false, v -> ia.handler.run(act, v)); + host.addView(arow, host.indexOfChild(about) + 1); }); }); } - // K2GO-449: an installed content module's update action comes from the ModuleActions - // registry (code_addons, code_assets, ...), so this sheet no longer carries one if per - // module. Forgejo's repos action stays special above (status-gated). - ModuleActions.InstalledAction ia = ModuleActions.installed(key); - if (ia != null) { - content.addView(row(ctx, ia.iconRes, act.getString(ia.labelRes), - Emphasis.ACCENT, null, false, v -> ia.handler.run(act, v))); - } break; case SCHEDULED: { content.addView(about); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActions.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActions.java index 417f28a3..c3fad5aa 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActions.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActions.java @@ -7,8 +7,11 @@ * action shown once the module is installed), so ModuleActionSheet and * ModuleDetailFragment iterate it instead of each carrying a per-module if block. A new * content module registers an entry here; the shared UI files stop changing per module. - * Static config only, no runtime state. Forgejo's repos action stays special for now (it - * is status-gated / async), so it is not in this registry yet. + * Static config only, no runtime state. An entry may carry a Gate: a check run off the + * main thread (it may hit the box) that decides whether to offer the action right now. + * Forgejo's "Update repos" is such a gated entry. Forgejo's RICHER detail-screen flow + * (install-repos / blocked / update, the repos opt-in checkbox, and the seed prefs) is + * intrinsically Forgejo-specific and stays in ModuleDetailFragment, not here. * ============================================================================ */ package org.appdevforall.k2go.redesign; @@ -30,13 +33,21 @@ private ModuleActions() {} /** Runs a module's installed action, anchored to a view (for the snackbar / inline progress). */ public interface Handler { void run(Activity act, View anchor); } - /** The update action a module offers once installed: a labeled, iconed row that runs {@link #handler}. */ + /** Decides whether an installed action should be offered now. Run OFF the main thread (it may hit + * the box), so a module can gate its action on live server status. */ + public interface Gate { boolean canShow(); } + + /** The update action a module offers once installed: a labeled, iconed row that runs {@link #handler}. + * When {@link #gate} is null the row always shows; otherwise the UI runs the gate off the main thread + * and shows the row only if it returns true. */ public static final class InstalledAction { public final int labelRes; public final int iconRes; public final Handler handler; - InstalledAction(int labelRes, int iconRes, Handler handler) { - this.labelRes = labelRes; this.iconRes = iconRes; this.handler = handler; + @Nullable public final Gate gate; + InstalledAction(int labelRes, int iconRes, Handler handler) { this(labelRes, iconRes, handler, null); } + InstalledAction(int labelRes, int iconRes, Handler handler, @Nullable Gate gate) { + this.labelRes = labelRes; this.iconRes = iconRes; this.handler = handler; this.gate = gate; } } @@ -49,9 +60,21 @@ public static final class InstalledAction { INSTALLED.put("code_assets", new InstalledAction( R.string.k2go_code_assets_update, R.drawable.ic_refresh, (act, v) -> org.appdevforall.k2go.codeassets.presentation.CodeAssetsRefresh.start(act, v))); + // K2GO-422 / K2GO-449: Forgejo "Update repos", gated on live box status (manageable AND repos + // present). The gate reads ForgejoStatusClient off the main thread. The module detail screen keeps + // its own richer Forgejo flow (install-repos / blocked / update plus the repos opt-in); this entry + // covers the simple gated action the module action sheet offers. + INSTALLED.put("forgejo", new InstalledAction( + R.string.k2go_forgejo_update_repos, R.drawable.ic_refresh, + (act, v) -> org.appdevforall.k2go.forgejo.presentation.ForgejoRepoRefresh.start(act, v), + () -> { + org.appdevforall.k2go.forgejo.data.ForgejoStatusClient.Status st = + new org.appdevforall.k2go.forgejo.data.ForgejoStatusClient().fetch(); + return st != null && st.canUpdateRepos(); + })); } - /** The installed action for {@code key}, or null when the module has none (or is handled specially). */ + /** The installed action for {@code key}, or null when the module has none. */ @Nullable public static InstalledAction installed(@Nullable String key) { return key == null ? null : INSTALLED.get(key); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java index 7577040e..511cf728 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java @@ -198,10 +198,10 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c }); } // K2GO-449: an installed content module's update action comes from the ModuleActions - // registry (code_addons, code_assets, ...), so this fragment no longer carries one if - // per module. Forgejo's repos action stays special above (status-gated). + // registry (code_addons, code_assets, ...). A gated entry (forgejo) is handled by the + // module's own status flow above, so apply only the plain (ungated) entries here. ModuleActions.InstalledAction ia = ModuleActions.installed(c.key()); - if (ia != null) { + if (ia != null && ia.gate == null) { installNowBtn.setText(ia.labelRes); installNowBtn.setOnClickListener(v -> ia.handler.run(requireActivity(), installNowBtn)); installNowBtn.setVisibility(View.VISIBLE);