|
| 1 | +/* |
| 2 | + * ============================================================================ |
| 3 | + * Name : DashNodeGate.java |
| 4 | + * Author : AppDevForAll |
| 5 | + * Copyright : Copyright (c) 2026 AppDevForAll |
| 6 | + * Description : K2GO-430. Gate a module install on the box dash-node version. Two mechanisms: |
| 7 | + * HARD block : a module that declares a minimum (DashNodeRequirement, e.g. Forgejo -> 1.3.7) |
| 8 | + * cannot install on a box below it; show a dialog and route to the dashboard update. |
| 9 | + * SOFT suggest : for a batch, when a dashboard update is available, recommend updating first |
| 10 | + * (Update / Install anyway). Reuses the existing version read (DashboardVersion, from the |
| 11 | + * rootfs, no network/proot) and the cached update-check (UpdateStatusCache), so the gate is |
| 12 | + * synchronous. |
| 13 | + * ============================================================================ |
| 14 | + */ |
| 15 | +package org.appdevforall.k2go.dependency.presentation; |
| 16 | + |
| 17 | +import android.app.Activity; |
| 18 | +import android.content.Context; |
| 19 | +import android.content.Intent; |
| 20 | + |
| 21 | +import androidx.annotation.NonNull; |
| 22 | +import androidx.annotation.Nullable; |
| 23 | + |
| 24 | +import org.appdevforall.k2go.R; |
| 25 | +import org.appdevforall.k2go.dependency.domain.DashNodeRequirement; |
| 26 | +import org.appdevforall.k2go.redesign.DashboardVersion; |
| 27 | +import org.appdevforall.k2go.redesign.OpReturnNavigator; |
| 28 | +import org.appdevforall.k2go.redesign.UpdateStatusCache; |
| 29 | +import org.appdevforall.k2go.ui.dialog.BrandDialog; |
| 30 | + |
| 31 | +public final class DashNodeGate { |
| 32 | + private DashNodeGate() {} |
| 33 | + |
| 34 | + /** True when the box dash-node is below {@code moduleKey}'s hard minimum (null key / no minimum = false). |
| 35 | + * A null (unreadable) installed version blocks a module that HAS a minimum: do not install it against |
| 36 | + * a dashboard we cannot verify. */ |
| 37 | + public static boolean installBlocked(@NonNull Context ctx, @Nullable String moduleKey) { |
| 38 | + int[] min = DashNodeRequirement.minFor(moduleKey); |
| 39 | + return min != null && !DashboardVersion.atLeast(DashboardVersion.installed(ctx), min[0], min[1], min[2]); |
| 40 | + } |
| 41 | + |
| 42 | + /** The first key below its dash-node minimum, or null. */ |
| 43 | + @Nullable |
| 44 | + private static String firstBlocked(@NonNull Context ctx, @Nullable String[] keys) { |
| 45 | + if (keys == null) return null; |
| 46 | + for (String k : keys) if (installBlocked(ctx, k)) return k; |
| 47 | + return null; |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * Gate a module install batch before it runs. If any banked module needs a newer dash-node than the |
| 52 | + * box has, block and route to the dashboard update. Otherwise, if a dashboard update is available, |
| 53 | + * suggest it first (Update / Install anyway). Otherwise run {@code onProceed} straight away. |
| 54 | + */ |
| 55 | + public static void guardModuleBatch(@NonNull Activity act, @Nullable String[] bankedKeys, @NonNull Runnable onProceed) { |
| 56 | + String blocked = firstBlocked(act, bankedKeys); |
| 57 | + if (blocked != null) { showBlocked(act, blocked); return; } |
| 58 | + if (UpdateStatusCache.updateAvailable(act)) { showSuggest(act, onProceed); return; } |
| 59 | + onProceed.run(); |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Gate a single-module (re)install, e.g. a retry: returns false (and shows the block dialog) when the |
| 64 | + * module is below its dash-node minimum, true when it may proceed. Hard block only (a retry does not |
| 65 | + * carry the soft update suggestion). Covers the install paths that start the index directly, not via |
| 66 | + * the batch gate. |
| 67 | + */ |
| 68 | + public static boolean allowInstall(@NonNull Context ctx, @Nullable String moduleKey) { |
| 69 | + if (installBlocked(ctx, moduleKey)) { showBlocked(ctx, moduleKey); return false; } |
| 70 | + return true; |
| 71 | + } |
| 72 | + |
| 73 | + private static void showBlocked(@NonNull Context ctx, @NonNull String moduleKey) { |
| 74 | + new BrandDialog(ctx) |
| 75 | + .setTitle(R.string.k2go_dep_required_title) |
| 76 | + .setMessage(ctx.getString(R.string.k2go_dep_required_msg_fmt, DashNodeRequirement.minLabel(moduleKey))) |
| 77 | + .setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(ctx)) |
| 78 | + .setNegative(android.R.string.cancel, null) |
| 79 | + .show(); |
| 80 | + } |
| 81 | + |
| 82 | + private static void showSuggest(@NonNull Context ctx, @NonNull Runnable onProceed) { |
| 83 | + new BrandDialog(ctx) |
| 84 | + .setTitle(R.string.k2go_dep_suggest_title) |
| 85 | + .setMessage(R.string.k2go_dep_suggest_msg) |
| 86 | + .setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(ctx)) |
| 87 | + .setNegative(R.string.k2go_dep_install_anyway, onProceed::run) |
| 88 | + // The suggestion is optional: dismissing it (back / tap-outside) proceeds with the install |
| 89 | + // the user already asked for, rather than silently swallowing it. |
| 90 | + .setOnCancel(onProceed::run) |
| 91 | + .show(); |
| 92 | + } |
| 93 | + |
| 94 | + /** Open the dashboard detail (the Rebuild/Update card) so the user can update the dash-node. Reuses the |
| 95 | + * canonical navigation intent instead of rebuilding it here. */ |
| 96 | + private static void openDashboard(@NonNull Context ctx) { |
| 97 | + Intent i = OpReturnNavigator.dashboardRebuild(ctx); |
| 98 | + if (!(ctx instanceof Activity)) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 99 | + ctx.startActivity(i); |
| 100 | + } |
| 101 | +} |
0 commit comments