Skip to content

Commit ce4b54f

Browse files
Merge pull request #541 from appdevforall/refactor/K2GO-385-pill-roles-modulehub
K2GO-385 refactor(ui): dissolve the overloaded statePill into status badge + action buttons (ModuleHub)
2 parents 9945270 + 07b93a5 commit ce4b54f

2 files changed

Lines changed: 87 additions & 23 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* ============================================================================
3+
* Name : K2GoStatusBadge.java
4+
* Author : AppDevForAll
5+
* Copyright : Copyright (c) 2026 AppDevForAll
6+
* Description : K2GO-385 (PR3). The shared read-only status badge: a semantic-coloured dot + label,
7+
* the pattern LibraryHomeFragment already uses on the home cards. Per the pill-roles
8+
* design decision a lifecycle state (Installed / Not installed / Failed / Scheduled)
9+
* reads as a STATUS — a dot + text, no ripple — distinct on purpose from an action
10+
* button (a tap target) and a metadata tag (neutral facts, see K2GoChip). Built once
11+
* here instead of the overloaded ModuleHubFragment.statePill it replaces. Pure UI; no
12+
* domain/data dependencies.
13+
* ============================================================================
14+
*/
15+
package org.appdevforall.k2go.redesign;
16+
17+
import android.content.Context;
18+
import android.graphics.drawable.GradientDrawable;
19+
import android.view.Gravity;
20+
import android.view.View;
21+
import android.widget.LinearLayout;
22+
import android.widget.TextView;
23+
24+
import androidx.annotation.ColorRes;
25+
import androidx.core.content.ContextCompat;
26+
27+
public final class K2GoStatusBadge {
28+
29+
private K2GoStatusBadge() {}
30+
31+
/**
32+
* Build a [dot + text] status badge for a horizontal row: a 9dp semantic-coloured dot, a 6dp
33+
* gap, and a Material 3 LabelMedium label in the same colour (dot and text match at AA). Not
34+
* clickable — a status, not an action. The caller sets the badge's own LayoutParams via
35+
* {@code addView(badge, params)}.
36+
*/
37+
public static LinearLayout create(Context context, CharSequence text, @ColorRes int colorRes) {
38+
float d = context.getResources().getDisplayMetrics().density;
39+
LinearLayout badge = new LinearLayout(context);
40+
badge.setOrientation(LinearLayout.HORIZONTAL);
41+
badge.setGravity(Gravity.CENTER_VERTICAL);
42+
43+
View dot = new View(context);
44+
int size = Math.round(9 * d);
45+
LinearLayout.LayoutParams dp = new LinearLayout.LayoutParams(size, size);
46+
dp.rightMargin = Math.round(6 * d);
47+
badge.addView(dot, dp);
48+
49+
TextView label = new TextView(context);
50+
label.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium);
51+
badge.addView(label);
52+
53+
style(badge, text, colorRes);
54+
return badge;
55+
}
56+
57+
/**
58+
* (Re)apply the badge's text and semantic colour in place — recolours the dot fill and the
59+
* label without rebuilding the view (mirrors {@link K2GoChip#style} for a live-updating status).
60+
*/
61+
public static void style(LinearLayout badge, CharSequence text, @ColorRes int colorRes) {
62+
int color = ContextCompat.getColor(badge.getContext(), colorRes);
63+
View dot = badge.getChildAt(0);
64+
TextView label = (TextView) badge.getChildAt(1);
65+
GradientDrawable circle = new GradientDrawable();
66+
circle.setShape(GradientDrawable.OVAL);
67+
circle.setColor(color);
68+
dot.setBackground(circle);
69+
label.setText(text);
70+
label.setTextColor(color);
71+
}
72+
}

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

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import androidx.core.content.ContextCompat;
3434
import androidx.fragment.app.Fragment;
3535

36+
import com.google.android.material.button.MaterialButton;
37+
3638
import org.appdevforall.k2go.R;
3739
import org.appdevforall.k2go.config.BoxEndpoints;
3840
import org.appdevforall.k2go.util.AppExecutors;
@@ -98,24 +100,6 @@ public class ModuleHubFragment extends Fragment {
98100
private LinearLayout host;
99101
private Button proceed;
100102

101-
/** ADFA-4958 §5.2: outlined state pill (transparent fill, state-colored 1.4dp stroke, full radius). */
102-
private TextView statePill(String text, int colorRes) {
103-
int color = ContextCompat.getColor(requireContext(), colorRes);
104-
TextView pill = new TextView(requireContext());
105-
pill.setText(text);
106-
pill.setTextColor(color);
107-
pill.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium);
108-
pill.setPadding(px(10), px(3), px(10), px(3));
109-
android.graphics.drawable.GradientDrawable bg = new android.graphics.drawable.GradientDrawable();
110-
bg.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE);
111-
bg.setColor(android.graphics.Color.TRANSPARENT);
112-
bg.setCornerRadius(px(20));
113-
int strokeW = Math.max(1, Math.round(1.4f * getResources().getDisplayMetrics().density));
114-
bg.setStroke(strokeW, color);
115-
pill.setBackground(bg);
116-
return pill;
117-
}
118-
119103
private int px(int dp) { return Math.round(dp * getResources().getDisplayMetrics().density); }
120104

121105
private static boolean is64Bit() {
@@ -393,8 +377,11 @@ private void addHiddenSection() {
393377
col.addView(sub);
394378
row.addView(col, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
395379

396-
TextView restore = statePill(getString(R.string.k2go_mod_restore), R.color.k2go_teal); // ADFA-4958 §5.7: outlined teal pill
397-
restore.setPadding(px(14), px(6), px(14), px(6));
380+
// K2GO-385 (PR3): an action is a real M3 button (filled primary stadium), not an outlined
381+
// pill — its shape signals "tappable" (the pill-roles design decision, board §"THE FOUR ROLES").
382+
MaterialButton restore = new MaterialButton(
383+
new android.view.ContextThemeWrapper(requireContext(), R.style.ThemeOverlay_K2Go_Button_Filled), null);
384+
restore.setText(R.string.k2go_mod_restore);
398385
restore.setOnClickListener(v -> { HiddenModules.remove(requireContext(), key); buildCards(); });
399386
row.addView(restore);
400387
host.addView(row);
@@ -486,7 +473,9 @@ private View cardRow(final ModuleCards.Card c, final boolean isInstalled,
486473
// already there cannot meaningfully be waiting to be installed, and if a stale wishlist
487474
// entry survived an install, saying "Scheduled" over it would be the older lie again.
488475
boolean scheduled = !isInstalled && !unknown && ModuleWishlist.contains(requireContext(), c.key());
489-
TextView pill = statePill(
476+
// K2GO-385 (PR3): a lifecycle state is a read-only STATUS -- dot + text, semantic colour, no
477+
// ripple (the shared K2GoStatusBadge) -- not an outlined pill that reads like a button.
478+
LinearLayout pill = K2GoStatusBadge.create(requireContext(),
490479
isInstalled ? getString(R.string.k2go_mod_phase_done)
491480
: failed ? getString(R.string.k2go_mod_phase_failed)
492481
: unknown ? getString(R.string.k2go_state_no_answer)
@@ -544,8 +533,11 @@ private void addSystemDashboardCard() {
544533
}
545534
});
546535

547-
TextView rebuild = statePill(getString(R.string.k2go_dash_rebuild), R.color.k2go_teal);
548-
rebuild.setPadding(px(14), px(6), px(14), px(6));
536+
// K2GO-385 (PR3): the Rebuild/Update action is a real M3 button (filled primary stadium), not
537+
// an outlined pill. Its label still toggles Rebuild<->Update via DashboardCardStatus below.
538+
MaterialButton rebuild = new MaterialButton(
539+
new android.view.ContextThemeWrapper(requireContext(), R.style.ThemeOverlay_K2Go_Button_Filled), null);
540+
rebuild.setText(R.string.k2go_dash_rebuild);
549541
// ADFA-5339: the confirm dialog matches the pill — pass the last-known "update available".
550542
final boolean[] up = {false};
551543
rebuild.setOnClickListener(v -> DashboardRebuild.confirmAndStart(this, host, up[0]));

0 commit comments

Comments
 (0)