diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java index 760d5a203..5f174a66b 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java @@ -196,9 +196,11 @@ private void populateCards() { // K2GO-445: the group's members (the IDE, its add-ons, its build assets), same filtering as the // grid. If all are filtered out, drop the empty group tile too. + // K2GO-443: each member carries its own glyph (they shared the code "" before): Code on the Go + // = code, Add-ons = extension (puzzle), Build assets = box. Drives the row AND the action-sheet header. codeDevMembers.add(new Card("code", getString(R.string.k2go_card_code), false, R.drawable.ic_card_code)); - codeDevMembers.add(new Card("code-addons", getString(R.string.k2go_card_code_addons), false, R.drawable.ic_card_code)); - codeDevMembers.add(new Card("code-assets", getString(R.string.k2go_card_code_assets), false, R.drawable.ic_card_code)); + codeDevMembers.add(new Card("code-addons", getString(R.string.k2go_card_code_addons), false, R.drawable.ic_card_addons)); + codeDevMembers.add(new Card("code-assets", getString(R.string.k2go_card_code_assets), false, R.drawable.ic_card_assets)); filterHidden(codeDevMembers); filterHidden(cards); if (codeDevMembers.isEmpty()) { @@ -500,6 +502,21 @@ private void openCodeDevSheet() { TextView status = new TextView(ctx); status.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall); rowv.addView(status); + // K2GO-443: per-member overflow menu, between the status and the open arrow (status -> menu -> + // arrow). It opens the member's action sheet (Open / About / Update / Hide) -- the only way + // to reach "Update" for a grouped member, since a row tap routes a ready member straight to its + // content. Material more_vert vector (not a text glyph) in a 48dp touch target, tinted like the + // other sheet icons. + ImageView menu = new ImageView(ctx); + menu.setImageResource(R.drawable.ic_more_vert); + menu.setColorFilter(ContextCompat.getColor(ctx, R.color.k2go_muted)); + menu.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + menu.setContentDescription(getString(R.string.k2go_sheet_more)); + menu.setClickable(true); menu.setFocusable(true); + menu.setOnClickListener(v -> { dlg.dismiss(); openSheet(m); }); + LinearLayout.LayoutParams mlp = new LinearLayout.LayoutParams(dpx(48), dpx(48)); + mlp.leftMargin = dpx(4); + rowv.addView(menu, mlp); TextView chev = new TextView(ctx); chev.setText("›"); chev.setTextSize(18); chev.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_muted)); diff --git a/controller/app/src/main/res/drawable/ic_card_addons.xml b/controller/app/src/main/res/drawable/ic_card_addons.xml new file mode 100644 index 000000000..bbda08687 --- /dev/null +++ b/controller/app/src/main/res/drawable/ic_card_addons.xml @@ -0,0 +1,4 @@ + + + + diff --git a/controller/app/src/main/res/drawable/ic_card_assets.xml b/controller/app/src/main/res/drawable/ic_card_assets.xml new file mode 100644 index 000000000..23395cfed --- /dev/null +++ b/controller/app/src/main/res/drawable/ic_card_assets.xml @@ -0,0 +1,4 @@ + + + + diff --git a/controller/app/src/main/res/drawable/ic_more_vert.xml b/controller/app/src/main/res/drawable/ic_more_vert.xml new file mode 100644 index 000000000..5aa80b962 --- /dev/null +++ b/controller/app/src/main/res/drawable/ic_more_vert.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tools/upstream-patches/overlays/roles/code_addons/files/mirror_addons.py b/tools/upstream-patches/overlays/roles/code_addons/files/mirror_addons.py index eb4e99828..ec04de95b 100644 --- a/tools/upstream-patches/overlays/roles/code_addons/files/mirror_addons.py +++ b/tools/upstream-patches/overlays/roles/code_addons/files/mirror_addons.py @@ -303,16 +303,16 @@ def _stage(source_base, serve_base, out, reuse_from, heavy_sink, limit_large, lo catalog_bytes = fetch(f"{source_base}/{CATALOG}") rewritten_catalog = catalog_bytes.decode("utf-8").replace(source_base, serve_base) - # K2GO-441: same-build short-circuit. The catalog carries `generated` (a build timestamp), so a - # byte-identical catalog means the exact same published build: nothing to download, nothing to swap. + # K2GO-441: same published build when the served catalog is byte-identical (the catalog carries a + # `generated` build timestamp). Computed here, but the short-circuit below ALSO requires every planned + # file to be present, so a file lost out-of-band (interrupted swap, disk) still re-downloads. + catalog_same = False if reuse_from is not None: try: - if (reuse_from / CATALOG).read_text(encoding="utf-8") == rewritten_catalog: - return {"up_to_date": True, "downloaded": 0, "reused": 0, "failed": 0, "headed": 0} + catalog_same = (reuse_from / CATALOG).read_text(encoding="utf-8") == rewritten_catalog except OSError: - pass # no served catalog yet: fall through to a full mirror. + catalog_same = False # no served catalog yet: fall through to a full mirror. - out.mkdir(parents=True, exist_ok=True) catalog = json.loads(catalog_bytes) served = served_catalog_shas(reuse_from) @@ -329,6 +329,16 @@ def _stage(source_base, serve_base, out, reuse_from, heavy_sink, limit_large, lo paths, sha, size, reuse_ok = build_plan( source_base, catalog, served, index_unchanged, index_bytes) + + # K2GO-443: presence-aware same-build short-circuit. Up to date ONLY when the published build is + # unchanged AND every planned file is still present in the served tree, so a missing/corrupt file + # re-downloads instead of being reported up-to-date (build-assets' short-circuit is likewise + # presence-aware). Reads are cheap here; the expensive staging copy is what this still skips. + if (reuse_from is not None and catalog_same and index_unchanged + and all((reuse_from / rel).is_file() for rel in paths)): + return {"up_to_date": True, "downloaded": 0, "reused": 0, "failed": 0, "headed": 0} + + out.mkdir(parents=True, exist_ok=True) log(f"plan: {len(paths)} files, {len(sha)} verified binaries, " f"{len(catalog['addons'])} add-ons")