From b13151d21734db957fc07ce772de9f6cf28afc5a Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 4 Sep 2026 02:01:52 -0600 Subject: [PATCH] K2GO-385 refactor(ui): extract shared K2GoChip and migrate the duplicated outlined chips The outlined status/meta chip (transparent fill, colored 1.4dp stroke, M3 LabelMedium) was copy-pasted: DashboardDetailFragment built it via chip()/styleChip() and ModuleDetailFragment via an identical chip(), so a fix to one silently missed the other. Add redesign/K2GoChip with create(ctx, text, colorRes) to build a row chip and style(TextView, text, colorRes) to recolor one in place (for the live status pill), preserving the exact dp/appearance/outline. Migrate both fragments onto it and drop their private helpers (and now-orphaned imports). The card home dot+label is a different form with a single-owner state rule and stays out; a survey of the other GradientDrawable sites found no further instance of this chip (ModuleHubFragment.statePill is a distinct, overloaded full-radius pill, left as-is). --- .../redesign/DashboardDetailFragment.java | 47 +++----------- .../appdevforall/k2go/redesign/K2GoChip.java | 61 +++++++++++++++++++ .../k2go/redesign/ModuleDetailFragment.java | 42 +++---------- 3 files changed, 79 insertions(+), 71 deletions(-) create mode 100644 controller/app/src/main/java/org/appdevforall/k2go/redesign/K2GoChip.java diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardDetailFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardDetailFragment.java index 2b7f1059..abc3e1e5 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardDetailFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardDetailFragment.java @@ -106,10 +106,10 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c // ADFA-5026: the status chip starts at index 0 so that when the version chip prepends at 0 it // lands right after the version. "Runs offline" is replaced by the update-status pill. chips = root.findViewById(R.id.k2go_moddet_chips); - statusChip = chip(getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); + statusChip = K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); chips.addView(statusChip); - chips.addView(chip(getString(R.string.k2go_dash_chip_rest), R.color.k2go_teal)); - chips.addView(chip(getString(R.string.k2go_dash_chip_core), R.color.k2go_teal)); + chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_rest), R.color.k2go_teal)); + chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_core), R.color.k2go_teal)); fetchVersionChip(); ((TextView) root.findViewById(R.id.k2go_moddet_includes_body)).setText(R.string.k2go_dash_includes); @@ -188,13 +188,13 @@ private void applyCardState(DashboardCardState s) { if (statusChip != null) { switch (s.kind()) { case OFFLINE: - styleChip(statusChip, getString(R.string.k2go_dash_no_connection), R.color.k2go_muted); break; + K2GoChip.style(statusChip, getString(R.string.k2go_dash_no_connection), R.color.k2go_muted); break; case CHECKING: - styleChip(statusChip, getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); break; + K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); break; case UP_TO_DATE: - styleChip(statusChip, getString(R.string.k2go_dash_chip_uptodate), R.color.k2go_leaf); break; + K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_uptodate), R.color.k2go_leaf); break; case UPDATE_AVAILABLE: - styleChip(statusChip, getString(R.string.k2go_dash_chip_update), R.color.k2go_amber); break; + K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_update), R.color.k2go_amber); break; } statusChip.setVisibility(View.VISIBLE); } @@ -423,10 +423,10 @@ private void fetchVersionChip() { main.post(() -> { if (!isAdded() || chips == null || ver == null) return; if (versionChip == null) { - versionChip = chip("v" + ver, R.color.k2go_teal); + versionChip = K2GoChip.create(requireContext(), "v" + ver, R.color.k2go_teal); chips.addView(versionChip, 0); } else { - styleChip(versionChip, "v" + ver, R.color.k2go_teal); + K2GoChip.style(versionChip, "v" + ver, R.color.k2go_teal); } }); }); @@ -439,33 +439,4 @@ private void refreshAfterLiveUpdate() { fetchUpdateStatus(); } - /** Small outlined pill for the meta-chip row (matches ModuleDetailFragment). */ - private TextView chip(String text, int colorRes) { - float d = getResources().getDisplayMetrics().density; - TextView t = new TextView(requireContext()); - t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium); - int hp = Math.round(10 * d), vp = Math.round(5 * d); - t.setPadding(hp, vp, hp, vp); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - lp.rightMargin = Math.round(8 * d); - t.setLayoutParams(lp); - styleChip(t, text, colorRes); - return t; - } - - /** (Re)apply a chip's text + outlined color. ADFA-5026: split out of {@link #chip} so the live - * status pill can change text/color in place without rebuilding the view. */ - private void styleChip(TextView t, String text, int colorRes) { - float d = getResources().getDisplayMetrics().density; - int color = ContextCompat.getColor(requireContext(), colorRes); - t.setText(text); - t.setTextColor(color); - android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable(); - bg.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE); - bg.setColor(android.graphics.Color.TRANSPARENT); - bg.setCornerRadius(11 * d); - bg.setStroke(Math.max(1, Math.round(1.4f * d)), color); - t.setBackground(bg); - } } diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/K2GoChip.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/K2GoChip.java new file mode 100644 index 00000000..7631f2e7 --- /dev/null +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/K2GoChip.java @@ -0,0 +1,61 @@ +/* + * ============================================================================ + * Name : K2GoChip.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : K2GO-385. The shared outlined status/meta chip, so the same small pill is defined + * ONCE instead of being copied per fragment. A transparent, rounded, colored-outline + * Material 3 LabelMedium pill (ADFA-4958 §5.4: a filled teal-on-teal chip was + * invisible, hence the outline). Pure UI; no domain/data dependencies. + * ============================================================================ + */ +package org.appdevforall.k2go.redesign; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.ColorRes; +import androidx.core.content.ContextCompat; + +public final class K2GoChip { + + private K2GoChip() {} + + /** + * Build a chip laid out for a horizontal meta-chip row: WRAP content, an 8dp end margin, and + * the shared outline applied. Callers add it to a {@link LinearLayout} row. + */ + public static TextView create(Context context, CharSequence text, @ColorRes int colorRes) { + float d = context.getResources().getDisplayMetrics().density; + TextView t = new TextView(context); + t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium); + int hp = Math.round(10 * d), vp = Math.round(5 * d); + t.setPadding(hp, vp, hp, vp); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + lp.rightMargin = Math.round(8 * d); + t.setLayoutParams(lp); + style(t, text, colorRes); + return t; + } + + /** + * (Re)apply a chip's text and outlined color in place, without rebuilding the view (ADFA-5026: + * a live status pill recolors between checking/up-to-date/update states as it refreshes). + */ + public static void style(TextView chip, CharSequence text, @ColorRes int colorRes) { + float d = chip.getResources().getDisplayMetrics().density; + int color = ContextCompat.getColor(chip.getContext(), colorRes); + chip.setText(text); + chip.setTextColor(color); + GradientDrawable bg = new GradientDrawable(); + bg.setShape(GradientDrawable.RECTANGLE); + bg.setColor(Color.TRANSPARENT); + bg.setCornerRadius(11 * d); + bg.setStroke(Math.max(1, Math.round(1.4f * d)), color); + chip.setBackground(bg); + } +} 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 181eda2e..57e97ff8 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 @@ -20,12 +20,10 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; -import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.core.content.ContextCompat; import org.appdevforall.k2go.R; import org.appdevforall.k2go.util.Snackbars; @@ -72,11 +70,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c String sizeText = (sizeRes != 0) ? getString(sizeRes) : (bytes >= 0) ? "\u2248 " + org.appdevforall.k2go.util.ByteFormatter.toHuman(bytes) : "\u2248 NA"; - chips.addView(chip(sizeText, R.color.k2go_teal)); + chips.addView(K2GoChip.create(requireContext(), sizeText, R.color.k2go_teal)); String ver = ModuleCards.version(c.key()); - if (ver != null) chips.addView(chip("v" + ver, R.color.k2go_teal)); - chips.addView(chip(getString(R.string.k2go_mod_runs_offline), R.color.k2go_leaf)); - if (ModuleCards.isDemo(c.key())) chips.addView(chip(getString(R.string.k2go_mod_demo), R.color.k2go_amber_text)); // ADFA-4958 + if (ver != null) chips.addView(K2GoChip.create(requireContext(), "v" + ver, R.color.k2go_teal)); + chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_runs_offline), R.color.k2go_leaf)); + if (ModuleCards.isDemo(c.key())) chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_demo), R.color.k2go_amber_text)); // ADFA-4958 int inc = ModuleCards.includesRes(c.key()); if (inc != 0) { @@ -125,7 +123,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c if (!isAdded()) return; if (verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.NO_SYSTEM || verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.DAMAGED) { - chipRow.addView(chip(getString(R.string.k2go_state_no_system), R.color.k2go_amber_text)); + chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_state_no_system), R.color.k2go_amber_text)); installNowBtn.setText(R.string.k2go_home_recover); installNowBtn.setOnClickListener(v -> SetupLibraryActivity.recover(requireContext())); installNowBtn.setVisibility(View.VISIBLE); @@ -136,11 +134,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c || verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.CLONE_SHARING) { // ADFA-5312: a system op is in progress — the system is present but mid-setup and the // server is down. Don't offer Install or Recover into it; just say it's busy. - chipRow.addView(chip(getString(R.string.k2go_home_installing), R.color.k2go_amber_text)); + chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_home_installing), R.color.k2go_amber_text)); return; } if (isInstalled) { - chipRow.addView(chip(getString(R.string.k2go_mod_phase_done), R.color.k2go_leaf)); + chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_phase_done), R.color.k2go_leaf)); return; // nothing to offer: a module cannot be uninstalled or reinstalled here } // ADFA-4898: this module's runrole failed in the last finished batch — the SAME per-module @@ -153,7 +151,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c // Retry then bounces to the hub, which observes the queue live; this detail is a one-shot // snapshot (no observer) and would otherwise sit on a stale "Couldn't install". if (org.appdevforall.k2go.install.presentation.ModuleQueueRepository.get().current().didFail(c.key())) { - chipRow.addView(chip(getString(R.string.k2go_mod_phase_failed), R.color.k2go_clay)); + chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_phase_failed), R.color.k2go_clay)); schedule.setText(R.string.k2go_home_retry); // Shared, busy-gated retry (same action the live progress card fires). On a real start, // land on the install index — the same destination as a normal install (openModuleIndex) @@ -172,7 +170,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return; } if (unknown) { - chipRow.addView(chip(getString(R.string.k2go_state_no_answer), + chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_state_no_answer), R.color.k2go_amber_text)); return; // no grounds to offer work either way } @@ -208,26 +206,4 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return root; } - /** A small outlined pill for the meta-chip row (size / version / Runs offline). */ - private TextView chip(String text, int colorRes) { - float d = getResources().getDisplayMetrics().density; - TextView t = new TextView(requireContext()); - t.setText(text); - t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium); - int color = ContextCompat.getColor(requireContext(), colorRes); // ADFA-4958 §5.4: outlined pill (teal-on-teal fill was invisible) - t.setTextColor(color); - android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable(); - bg.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE); - bg.setColor(android.graphics.Color.TRANSPARENT); - bg.setCornerRadius(11 * d); - bg.setStroke(Math.max(1, Math.round(1.4f * d)), color); - t.setBackground(bg); - int hp = Math.round(10 * d), vp = Math.round(5 * d); - t.setPadding(hp, vp, hp, vp); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - lp.rightMargin = Math.round(8 * d); - t.setLayoutParams(lp); - return t; - } }