Skip to content

Commit 98ccce9

Browse files
K2GO-430 fix(dependency): gate retries and proceed on suggestion dismiss
ModuleRetry.fire now checks DashNodeGate.allowInstall, since a retry starts the index directly and bypassed the batch gate. Dismissing the soft suggestion now proceeds with the install instead of swallowing it. Add a DashboardVersion.atLeast unit test.
1 parent f21bb98 commit 98ccce9

3 files changed

Lines changed: 87 additions & 17 deletions

File tree

‎controller/app/src/main/java/org/appdevforall/k2go/dependency/presentation/DashNodeGate.java‎

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33
* Name : DashNodeGate.java
44
* Author : AppDevForAll
55
* Copyright : Copyright (c) 2026 AppDevForAll
6-
* Description : K2GO-430. Gate a module install batch on the box dash-node version. Two mechanisms:
6+
* Description : K2GO-430. Gate a module install on the box dash-node version. Two mechanisms:
77
* HARD block : a module that declares a minimum (DashNodeRequirement, e.g. Forgejo -> 1.3.7)
88
* cannot install on a box below it; show a dialog and route to the dashboard update.
9-
* SOFT suggest : for any install, when a dashboard update is available, recommend updating
10-
* first (Update / Install anyway). Reuses the existing version read (DashboardVersion, from
11-
* the rootfs, no network/proot) and the cached update-check (UpdateStatusCache), so the gate
12-
* is synchronous.
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.
1313
* ============================================================================
1414
*/
1515
package org.appdevforall.k2go.dependency.presentation;
1616

1717
import android.app.Activity;
1818
import android.content.Context;
19+
import android.content.Intent;
1920

2021
import androidx.annotation.NonNull;
2122
import androidx.annotation.Nullable;
@@ -30,7 +31,9 @@
3031
public final class DashNodeGate {
3132
private DashNodeGate() {}
3233

33-
/** True when the box dash-node is below {@code moduleKey}'s hard minimum (null key / no minimum = false). */
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. */
3437
public static boolean installBlocked(@NonNull Context ctx, @Nullable String moduleKey) {
3538
int[] min = DashNodeRequirement.minFor(moduleKey);
3639
return min != null && !DashboardVersion.atLeast(DashboardVersion.installed(ctx), min[0], min[1], min[2]);
@@ -56,27 +59,43 @@ public static void guardModuleBatch(@NonNull Activity act, @Nullable String[] ba
5659
onProceed.run();
5760
}
5861

59-
private static void showBlocked(@NonNull Activity act, @NonNull String moduleKey) {
60-
new BrandDialog(act)
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)
6175
.setTitle(R.string.k2go_dep_required_title)
62-
.setMessage(act.getString(R.string.k2go_dep_required_msg_fmt, DashNodeRequirement.minLabel(moduleKey)))
63-
.setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(act))
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))
6478
.setNegative(android.R.string.cancel, null)
6579
.show();
6680
}
6781

68-
private static void showSuggest(@NonNull Activity act, @NonNull Runnable onProceed) {
69-
new BrandDialog(act)
82+
private static void showSuggest(@NonNull Context ctx, @NonNull Runnable onProceed) {
83+
new BrandDialog(ctx)
7084
.setTitle(R.string.k2go_dep_suggest_title)
7185
.setMessage(R.string.k2go_dep_suggest_msg)
72-
.setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(act))
86+
.setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(ctx))
7387
.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)
7491
.show();
7592
}
7693

77-
/** Open the dashboard detail (the Rebuild/Update card) so the user can update the dash-node. Reuses
78-
* the canonical navigation intent instead of rebuilding it here. */
79-
private static void openDashboard(@NonNull Activity act) {
80-
act.startActivity(OpReturnNavigator.dashboardRebuild(act));
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);
81100
}
82101
}

‎controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleRetry.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public static boolean fire(View anchor, String moduleKey) {
5454
Snackbars.make(anchor, BusyMessage.resFor(ctx)).show();
5555
return false;
5656
}
57+
// K2GO-430: a retry starts the install directly (not via openModuleIndex), so gate the dash-node
58+
// dependency here too. If the box dash-node is below the module's minimum, the gate shows the
59+
// "update the dashboard first" dialog and the retry does not fire.
60+
if (!org.appdevforall.k2go.dependency.presentation.DashNodeGate.allowInstall(ctx, moduleKey)) {
61+
return false;
62+
}
5763
InstallService.retryModules(ctx, Collections.singletonList(moduleKey));
5864
return true;
5965
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.appdevforall.k2go.redesign;
2+
3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import org.junit.Test;
7+
8+
/** K2GO-430: the version compare behind the dash-node dependency gate (installBlocked uses it). Pure,
9+
* so a plain JVM test; installed(Context) reads a file and is not covered here. */
10+
public class DashboardVersionTest {
11+
12+
@Test
13+
public void nullOrJunkIsBelowAnyMinimum() {
14+
assertFalse(DashboardVersion.atLeast(null, 1, 3, 7));
15+
assertFalse(DashboardVersion.atLeast("", 1, 3, 7));
16+
assertFalse(DashboardVersion.atLeast("not-a-version", 1, 3, 7));
17+
}
18+
19+
@Test
20+
public void exactMinimumMeetsIt() {
21+
assertTrue(DashboardVersion.atLeast("1.3.7", 1, 3, 7));
22+
}
23+
24+
@Test
25+
public void belowMinimumFails() {
26+
assertFalse(DashboardVersion.atLeast("1.3.6", 1, 3, 7));
27+
assertFalse(DashboardVersion.atLeast("1.2.9", 1, 3, 7));
28+
assertFalse(DashboardVersion.atLeast("0.9.9", 1, 3, 7));
29+
assertFalse(DashboardVersion.atLeast("1.3", 1, 3, 7)); // missing patch = .0, so 1.3.0 < 1.3.7
30+
}
31+
32+
@Test
33+
public void aboveMinimumPasses() {
34+
assertTrue(DashboardVersion.atLeast("1.3.8", 1, 3, 7));
35+
assertTrue(DashboardVersion.atLeast("1.3.10", 1, 3, 7)); // numeric compare, not lexical
36+
assertTrue(DashboardVersion.atLeast("1.4.0", 1, 3, 7));
37+
assertTrue(DashboardVersion.atLeast("2.0.0", 1, 3, 7));
38+
}
39+
40+
@Test
41+
public void suffixesAreIgnored() {
42+
assertTrue(DashboardVersion.atLeast("1.3.7-beta", 1, 3, 7));
43+
assertTrue(DashboardVersion.atLeast("1.4.0+build5", 1, 3, 7));
44+
}
45+
}

0 commit comments

Comments
 (0)