Skip to content

Commit 704bd46

Browse files
Merge pull request #542 from appdevforall/refactor/K2GO-385-pill-roles-detail
K2GO-385 refactor(ui): metadata tags + status badges in the detail screens (PR3b)
2 parents ce4b54f + d24d3ab commit 704bd46

5 files changed

Lines changed: 75 additions & 26 deletions

File tree

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

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class DashboardDetailFragment extends Fragment {
4545

4646
private final Handler main = new Handler(Looper.getMainLooper());
4747
private ViewGroup chips; // FlowLayout in XML — typed as ViewGroup so it wraps chips to 2 lines
48-
private TextView statusChip; // ADFA-5026: live "Up to date / Update available" pill (restyled in place)
48+
private LinearLayout statusChip; // K2GO-385 (PR3): live status badge (dot+text), restyled in place
4949
private TextView versionChip; // ADFA-5051: "v<version>" chip, updated in place after a live update
5050
private Button rebuild; // de-emphasized when already on the latest
5151
private TextView rebuildHint; // "no rebuild needed" note, shown only when on the latest
@@ -103,13 +103,16 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
103103
((TextView) root.findViewById(R.id.k2go_moddet_desc)).setText(R.string.k2go_dash_detail_desc);
104104

105105
// Meta chips, in order: version (prepended live) | update status (live) | REST API | System core.
106-
// ADFA-5026: the status chip starts at index 0 so that when the version chip prepends at 0 it
107-
// lands right after the version. "Runs offline" is replaced by the update-status pill.
108106
chips = root.findViewById(R.id.k2go_moddet_chips);
109-
statusChip = K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted);
110-
chips.addView(statusChip);
111-
chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_rest), R.color.k2go_teal));
112-
chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_core), R.color.k2go_teal));
107+
// K2GO-385 (PR3): the live lifecycle state is a status badge (dot+text, semantic) on its OWN line
108+
// below the metadata; version / REST API / System core are neutral metadata tags in the chip row
109+
// (colour there is noise -- the metadata colour rule). The version chip prepends to the chip row.
110+
ViewGroup statusRow = root.findViewById(R.id.k2go_moddet_status);
111+
statusRow.setVisibility(View.VISIBLE);
112+
statusChip = K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted);
113+
statusRow.addView(statusChip);
114+
chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_rest)));
115+
chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_dash_chip_core)));
113116
fetchVersionChip();
114117

115118
((TextView) root.findViewById(R.id.k2go_moddet_includes_body)).setText(R.string.k2go_dash_includes);
@@ -188,13 +191,13 @@ private void applyCardState(DashboardCardState s) {
188191
if (statusChip != null) {
189192
switch (s.kind()) {
190193
case OFFLINE:
191-
K2GoChip.style(statusChip, getString(R.string.k2go_dash_no_connection), R.color.k2go_muted); break;
194+
K2GoStatusBadge.style(statusChip, getString(R.string.k2go_dash_no_connection), R.color.k2go_muted); break;
192195
case CHECKING:
193-
K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); break;
196+
K2GoStatusBadge.style(statusChip, getString(R.string.k2go_dash_chip_checking), R.color.k2go_muted); break;
194197
case UP_TO_DATE:
195-
K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_uptodate), R.color.k2go_leaf); break;
198+
K2GoStatusBadge.style(statusChip, getString(R.string.k2go_dash_chip_uptodate), R.color.k2go_leaf); break;
196199
case UPDATE_AVAILABLE:
197-
K2GoChip.style(statusChip, getString(R.string.k2go_dash_chip_update), R.color.k2go_amber); break;
200+
K2GoStatusBadge.style(statusChip, getString(R.string.k2go_dash_chip_update), R.color.k2go_amber_text); break; // K2GO-385: amber_text (AA) + consistent with the other amber statuses
198201
}
199202
statusChip.setVisibility(View.VISIBLE);
200203
}
@@ -418,10 +421,10 @@ private void fetchVersionChip() {
418421
main.post(() -> {
419422
if (!isAdded() || chips == null || ver == null) return;
420423
if (versionChip == null) {
421-
versionChip = K2GoChip.create(requireContext(), "v" + ver, R.color.k2go_teal);
424+
versionChip = K2GoChip.create(requireContext(), "v" + ver);
422425
chips.addView(versionChip, 0);
423426
} else {
424-
K2GoChip.style(versionChip, "v" + ver, R.color.k2go_teal);
427+
K2GoChip.style(versionChip, "v" + ver);
425428
}
426429
});
427430
});

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
* Name : K2GoChip.java
44
* Author : AppDevForAll
55
* Copyright : Copyright (c) 2026 AppDevForAll
6-
* Description : K2GO-385. The shared outlined status/meta chip, so the same small pill is defined
7-
* ONCE instead of being copied per fragment. A transparent, rounded, colored-outline
8-
* Material 3 LabelMedium pill (ADFA-4958 §5.4: a filled teal-on-teal chip was
9-
* invisible, hence the outline). Pure UI; no domain/data dependencies.
6+
* Description : K2GO-385. The shared metadata TAG (pill-roles design decision, PR3): a quiet,
7+
* read-only, non-interactive label for facts -- version, size, "REST API", "System
8+
* core" -- and coloured trait badges that carry valence ("Runs offline" = leaf). An
9+
* 8dp-corner, monospace, transparent-outline Material 3 tag; the 8dp corner + mono +
10+
* no-dot mark it as data, distinct from the dot+text status (K2GoStatusBadge) and the
11+
* stadium action button. Colour is neutral by default, semantic only for valence
12+
* (the metadata colour rule). Pure UI; no domain/data dependencies.
1013
* ============================================================================
1114
*/
1215
package org.appdevforall.k2go.redesign;
@@ -20,10 +23,26 @@
2023
import androidx.annotation.ColorRes;
2124
import androidx.core.content.ContextCompat;
2225

26+
import org.appdevforall.k2go.R;
27+
2328
public final class K2GoChip {
2429

2530
private K2GoChip() {}
2631

32+
/** The neutral tag colour. Metadata is neutral by default (the metadata colour rule); pass an
33+
* explicit colour to the 3-arg overloads only for a valence trait (e.g. leaf "Runs offline"). */
34+
@ColorRes private static final int NEUTRAL = R.color.k2go_muted;
35+
36+
/** A neutral metadata tag (version, size, "REST API", "System core") -- the common case. */
37+
public static TextView create(Context context, CharSequence text) {
38+
return create(context, text, NEUTRAL);
39+
}
40+
41+
/** Re-apply a neutral metadata tag's text/colour in place. */
42+
public static void style(TextView chip, CharSequence text) {
43+
style(chip, text, NEUTRAL);
44+
}
45+
2746
/**
2847
* Build a chip laid out for a horizontal meta-chip row: WRAP content, an 8dp end margin, and
2948
* the shared outline applied. Callers add it to a {@link LinearLayout} row.
@@ -32,6 +51,7 @@ public static TextView create(Context context, CharSequence text, @ColorRes int
3251
float d = context.getResources().getDisplayMetrics().density;
3352
TextView t = new TextView(context);
3453
t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium);
54+
t.setTypeface(android.graphics.Typeface.MONOSPACE); // reads as a quiet data tag (the "mono" cue)
3555
int hp = Math.round(10 * d), vp = Math.round(5 * d);
3656
t.setPadding(hp, vp, hp, vp);
3757
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
@@ -54,7 +74,7 @@ public static void style(TextView chip, CharSequence text, @ColorRes int colorRe
5474
GradientDrawable bg = new GradientDrawable();
5575
bg.setShape(GradientDrawable.RECTANGLE);
5676
bg.setColor(Color.TRANSPARENT);
57-
bg.setCornerRadius(11 * d);
77+
bg.setCornerRadius(8 * d); // metadata tag corner (was 11dp) -- 8dp signals "tag", not a stadium pill
5878
bg.setStroke(Math.max(1, Math.round(1.4f * d)), color);
5979
chip.setBackground(bg);
6080
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ public static LinearLayout create(Context context, CharSequence text, @ColorRes
5050
label.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium);
5151
badge.addView(label);
5252

53+
// Self-space for a horizontal chip/flow row (8dp end gap, like K2GoChip); a caller that needs a
54+
// different margin passes its own LayoutParams to addView, which overrides this.
55+
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
56+
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
57+
lp.rightMargin = Math.round(8 * d);
58+
badge.setLayoutParams(lp);
59+
5360
style(badge, text, colorRes);
5461
return badge;
5562
}

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
7070
String sizeText = (sizeRes != 0) ? getString(sizeRes)
7171
: (bytes >= 0) ? "\u2248 " + org.appdevforall.k2go.util.ByteFormatter.toHuman(bytes)
7272
: "\u2248 NA";
73-
chips.addView(K2GoChip.create(requireContext(), sizeText, R.color.k2go_teal));
73+
// K2GO-385 (PR3): size / version are neutral metadata tags (colour is noise); "Runs offline" is a
74+
// leaf trait (a capability that carries valence). The lifecycle status below is a dot+text badge.
75+
chips.addView(K2GoChip.create(requireContext(), sizeText));
7476
String ver = ModuleCards.version(c.key());
75-
if (ver != null) chips.addView(K2GoChip.create(requireContext(), "v" + ver, R.color.k2go_teal));
77+
if (ver != null) chips.addView(K2GoChip.create(requireContext(), "v" + ver));
7678
chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_runs_offline), R.color.k2go_leaf));
7779
if (ModuleCards.isDemo(c.key())) chips.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_demo), R.color.k2go_amber_text)); // ADFA-4958
7880

@@ -97,7 +99,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
9799
// than start visible, because showing them and then taking them away is how a user ends
98100
// up tapping one in the gap.
99101
final Button installNowBtn = root.findViewById(R.id.k2go_moddet_install_now);
100-
final ViewGroup chipRow = chips;
102+
final ViewGroup statusRow = root.findViewById(R.id.k2go_moddet_status);
101103
installNowBtn.setVisibility(View.GONE);
102104

103105
Button schedule = root.findViewById(R.id.k2go_moddet_schedule);
@@ -123,7 +125,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
123125
if (!isAdded()) return;
124126
if (verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.NO_SYSTEM
125127
|| verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.DAMAGED) {
126-
chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_state_no_system), R.color.k2go_amber_text));
128+
addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_state_no_system), R.color.k2go_amber_text));
127129
installNowBtn.setText(R.string.k2go_home_recover);
128130
installNowBtn.setOnClickListener(v -> SetupLibraryActivity.recover(requireContext()));
129131
installNowBtn.setVisibility(View.VISIBLE);
@@ -134,11 +136,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
134136
|| verdict == org.appdevforall.k2go.system.domain.SystemVerdict.State.CLONE_SHARING) {
135137
// ADFA-5312: a system op is in progress — the system is present but mid-setup and the
136138
// server is down. Don't offer Install or Recover into it; just say it's busy.
137-
chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_home_installing), R.color.k2go_amber_text));
139+
addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_home_installing), R.color.k2go_amber_text));
138140
return;
139141
}
140142
if (isInstalled) {
141-
chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_phase_done), R.color.k2go_leaf));
143+
addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_mod_phase_done), R.color.k2go_leaf));
142144
return; // nothing to offer: a module cannot be uninstalled or reinstalled here
143145
}
144146
// ADFA-4898: this module's runrole failed in the last finished batch — the SAME per-module
@@ -151,7 +153,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
151153
// Retry then bounces to the hub, which observes the queue live; this detail is a one-shot
152154
// snapshot (no observer) and would otherwise sit on a stale "Couldn't install".
153155
if (org.appdevforall.k2go.install.presentation.ModuleQueueRepository.get().current().didFail(c.key())) {
154-
chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_mod_phase_failed), R.color.k2go_clay));
156+
addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_mod_phase_failed), R.color.k2go_clay));
155157
schedule.setText(R.string.k2go_home_retry);
156158
// Shared, busy-gated retry (same action the live progress card fires). On a real start,
157159
// land on the install index — the same destination as a normal install (openModuleIndex)
@@ -170,7 +172,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
170172
return;
171173
}
172174
if (unknown) {
173-
chipRow.addView(K2GoChip.create(requireContext(), getString(R.string.k2go_state_no_answer),
175+
addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_state_no_answer),
174176
R.color.k2go_amber_text));
175177
return; // no grounds to offer work either way
176178
}
@@ -206,4 +208,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
206208
return root;
207209
}
208210

211+
/** K2GO-385 (PR3): a lifecycle status badge goes on its own row (k2go_moddet_status), which stays
212+
* GONE until one is added -- so a dot+text status never shares the boxed-metadata chip row. */
213+
private static void addStatus(ViewGroup row, View badge) {
214+
row.setVisibility(View.VISIBLE);
215+
row.addView(badge);
216+
}
217+
209218
}

‎controller/app/src/main/res/layout/fragment_k2go_module_detail.xml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@
6666
android:layout_height="wrap_content"
6767
android:layout_marginTop="12dp" />
6868

69+
<!-- K2GO-385 (PR3): the lifecycle status sits on its OWN line below the metadata tags, so a
70+
dot+text status badge never shares a row (and a centre axis) with the boxed metadata tags.
71+
GONE until a status is added (some module states show none). -->
72+
<org.appdevforall.k2go.redesign.FlowLayout
73+
android:id="@+id/k2go_moddet_status"
74+
android:layout_width="match_parent"
75+
android:layout_height="wrap_content"
76+
android:layout_marginTop="8dp"
77+
android:visibility="gone" />
78+
6979
<TextView
7080
android:id="@+id/k2go_moddet_desc"
7181
android:layout_width="match_parent"

0 commit comments

Comments
 (0)