Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.appdevforall.k2go.R;
import org.appdevforall.k2go.applang.data.ContentLanguage;
import org.appdevforall.k2go.kolibri.domain.Channel;
import org.appdevforall.k2go.redesign.K2GoFilterChip;
import org.appdevforall.k2go.redesign.SetupLibraryActivity;
import org.appdevforall.k2go.redesign.ZimLanguageDialog;
import org.appdevforall.k2go.util.ByteFormatter;
Expand Down Expand Up @@ -80,8 +81,8 @@ public final class KolibriBrowseFragment extends Fragment {
private TextView storageLabel;
private ProgressBar storageBar;
private LinearLayout chips;
private TextView sortSize;
private TextView sortName;
private com.google.android.material.chip.Chip sortSize;
private com.google.android.material.chip.Chip sortName;
private LinearLayout list;
private Button review;

Expand Down Expand Up @@ -281,11 +282,10 @@ private void updateSortControls() {
pill(sortName, s.isName());
}

/** The one pill style in the app: teal filled when on, hairline outline when off. */
private void pill(TextView t, boolean on) {
t.setBackgroundResource(on ? R.drawable.k2go_chip_bg : R.drawable.k2go_pill_bg);
t.setTextColor(ContextCompat.getColor(requireContext(),
on ? android.R.color.white : R.color.k2go_ink));
// K2GO-385 (PR3): the shared filter chip (8dp, 32dp, check when active) -- the sort toggles now match
// the ZIM sort chips and the category/Books filters; the label still carries the sort direction.
private void pill(com.google.android.material.chip.Chip t, boolean on) {
K2GoFilterChip.style(t, on);
}

/**
Expand Down Expand Up @@ -456,25 +456,19 @@ private void buildChips(KolibriCatalogUiState s) {
}

private View chip(String label, final String group) {
TextView t = new TextView(requireContext());
t.setText(label);
t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
t.setGravity(Gravity.CENTER);
t.setMinHeight(px(48)); // tap target, even though the pill looks smaller
t.setPadding(px(14), px(8), px(14), px(8));
pill(t, group.equals(groupFilter));

// K2GO-385 (PR3): category chips use the shared filter chip too.
com.google.android.material.chip.Chip c = K2GoFilterChip.create(requireContext(), label,
group.equals(groupFilter), x -> {
if (!group.equals(groupFilter)) {
groupFilter = group;
render(vm.state().getValue());
}
});
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.rightMargin = px(8);
t.setLayoutParams(lp);
t.setOnClickListener(x -> {
if (!group.equals(groupFilter)) {
groupFilter = group;
render(vm.state().getValue());
}
});
return t;
c.setLayoutParams(lp);
return c;
}

private void updateStorage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import androidx.fragment.app.Fragment;

import org.appdevforall.k2go.PortalActivity;
import com.google.android.material.chip.Chip;

import org.appdevforall.k2go.R;
import org.appdevforall.k2go.config.BoxEndpoints;
import org.json.JSONArray;
Expand Down Expand Up @@ -200,22 +202,10 @@ private void openLanguagePicker() {
() -> { lang = ""; updateLangPill(); loadBooks(); });
}

private TextView chip(String text, boolean on, Runnable onClick) {
TextView t = new TextView(requireContext());
t.setText(text);
t.setPadding(px(14), px(8), px(14), px(8));
t.setBackgroundResource(on ? R.drawable.k2go_chip_bg : R.drawable.k2go_pill_bg);
// ADFA-5248: apply the text appearance FIRST, then the color. TextAppearance_Material3_*
// carries its own colorOnSurface, so setting the color before it silently overwrote the chip
// color (the real bug the ticket reported: onSurface flips with the theme, giving dark-on-
// dark-teal in light mode and light-on-aqua in dark mode — never legible on the teal fill).
// With the order fixed, k2go_on_teal sticks: it flips against the fill (light text on the
// dark-teal light-mode fill, dark text on the light-aqua dark-mode fill).
t.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
t.setTextColor(ContextCompat.getColor(requireContext(), on ? R.color.k2go_on_teal : R.color.k2go_ink));
t.setClickable(true);
t.setOnClickListener(v -> onClick.run());
return t;
// K2GO-385 (PR3): the shared filter chip (8dp corner, 32dp, check when selected). Replaces the
// per-screen k2go_chip_bg/k2go_pill_bg drawable pair that had drifted from the other filter surfaces.
private Chip chip(String text, boolean on, Runnable onClick) {
return K2GoFilterChip.create(requireContext(), text, on, v -> onClick.run());
}

private void loadLibrary() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,8 @@ private void render() {
updateBackGuard(); // ADFA-5151: keep the Back confinement in step with side + transfer state
if (showcode != null) { showcode.setVisibility(View.GONE); codeblock.setVisibility(View.GONE); }
if (stepTitle != null) { stepTitle.setVisibility(View.GONE); shareWifi.setVisibility(View.GONE); }
paintTab(tabSend, side == Side.SEND);
paintTab(tabReceive, side == Side.RECEIVE);
SegmentedTabs.paint(tabSend, side == Side.SEND);
SegmentedTabs.paint(tabReceive, side == Side.RECEIVE);

if (atFork) {
cloneHdr.setVisibility(View.VISIBLE);
Expand Down Expand Up @@ -638,8 +638,8 @@ private void render() {
// ADFA-5154: Send is two pages. Common chrome, then the page.
actionFooter.setVisibility(View.GONE); // ADFA-5154: default hidden; only Copy's states re-show it
netRow.setVisibility(View.VISIBLE);
paintTab(tabHotspot, mode == Mode.HOTSPOT);
paintTab(tabWifi, mode == Mode.WIFI);
SegmentedTabs.paint(tabHotspot, mode == Mode.HOTSPOT);
SegmentedTabs.paint(tabWifi, mode == Mode.WIFI);
steps.setVisibility(View.VISIBLE);
buildSteps();
advance.setVisibility(View.VISIBLE);
Expand Down Expand Up @@ -1569,11 +1569,6 @@ private View arrow() {
return a;
}

private void paintTab(TextView t, boolean on) {
t.setBackgroundResource(on ? R.drawable.k2go_primary_bg : 0);
t.setTextColor(ContextCompat.getColor(requireContext(), on ? R.color.k2go_on_teal : R.color.k2go_muted));
}

private void styleAdvance(boolean filled) {
// ADFA-5346: shape/size come from the style; only toggle the fill + label emphasis by state.
K2GoButtons.setFilledEmphasis(advance, filled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ public void onDestroyView() {

private void render() {
if (!isAdded() || secSingle == null) return;
paintTab(tabHotspot, mode == Mode.HOTSPOT);
paintTab(tabWifi, mode == Mode.WIFI);
SegmentedTabs.paint(tabHotspot, mode == Mode.HOTSPOT);
SegmentedTabs.paint(tabWifi, mode == Mode.WIFI);
finish.setVisibility(View.GONE);
connFooter.setVisibility(View.VISIBLE); // default; the no-system state hides it
advance.setVisibility(View.GONE);
Expand Down Expand Up @@ -358,11 +358,6 @@ private View arrow() {
return a;
}

private void paintTab(TextView t, boolean on) {
t.setBackgroundResource(on ? R.drawable.k2go_primary_bg : 0);
t.setTextColor(ContextCompat.getColor(requireContext(), on ? R.color.k2go_on_teal : R.color.k2go_muted));
}

private void styleAdvance(boolean filled) {
// ADFA-5346: shape/size from the style; only toggle the fill + label emphasis by state.
K2GoButtons.setFilledEmphasis(advance, filled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,15 @@ private void showOverlay(String name, long sizeBytes) {
// ADFA-4896: Stop/Retry beside Cancel. The label follows the reported state; the tap fires the
// matching verb and the poll (onPaused/onProgress) is the source of truth.
overlayStopped = false;
overlayStop = new MaterialButton(themed, null,
com.google.android.material.R.attr.materialButtonOutlinedStyle);
// K2GO-385 (PR3): the download controls use the app button system (K2Go outlined stadium) via the
// shared overlay, not a bare Material3 outlined button. FQR's overlay is a themed (day/night)
// surface, so the K2Go outlined style's theme teal is right here -- not the fixed boot tokens.
ContextThemeWrapper btnCtx = new ContextThemeWrapper(themed, R.style.ThemeOverlay_K2Go_Button_Outlined);
overlayStop = new MaterialButton(btnCtx, null);
overlayStop.setText(R.string.k2go_clone_stop_confirm);
overlayStop.setOnClickListener(v -> toggleStop());
row.addView(overlayStop);
MaterialButton cancel = new MaterialButton(themed, null,
com.google.android.material.R.attr.materialButtonOutlinedStyle);
MaterialButton cancel = new MaterialButton(btnCtx, null);
cancel.setText(R.string.k2go_cancel);
cancel.setOnClickListener(v -> { client.cancel(); hideOverlay(); });
row.addView(cancel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* ============================================================================
* Name : K2GoFilterChip.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : K2GO-385 (PR3). The shared FILTER CHIP (pill-roles design decision, board
* k2go-chip-vs-button-v1): a selectable/toggle chip -- Popular / Educational, a ZIM
* category, a sort order. Per the decision it is an 8dp-corner, ~32dp Material 3 chip
* with a leading check when selected -- "8dp corner + check = toggle", distinct on
* purpose from the stadium action button, the dot+text status and the 8dp metadata
* tag. Built ONCE here instead of the three drifted per-screen chip builders it
* replaces (ZimLanding / ZimCategory / BooksLanding). Colour is the app teal (filled +
* on-teal text when selected; transparent + teal outline + teal text when not). Pure
* UI; no domain/data dependencies.
* ============================================================================
*/
package org.appdevforall.k2go.redesign;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;

import androidx.core.content.ContextCompat;

import com.google.android.material.chip.Chip;

import org.appdevforall.k2go.R;

public final class K2GoFilterChip {

private K2GoFilterChip() {}

/**
* Build a selectable filter chip. {@code selected} sets the checked look (teal fill + check);
* {@code onClick} fires on tap -- the caller owns the selection model (usually rebuilding the
* row), so the chip is a view of that state, not the source of truth.
*/
public static Chip create(Context ctx, CharSequence label, boolean selected, View.OnClickListener onClick) {
Chip chip = new Chip(ctx);
chip.setText(label);
style(chip, selected);
chip.setOnClickListener(onClick);
return chip;
}

/**
* Apply the filter-chip look + selected state to an EXISTING chip (e.g. one inflated from XML,
* like the ZimCategory sort chips). Idempotent -- safe to call on every render. The caller owns
* the text (so a sort chip can carry its "By size ▲" direction) and the click.
*/
public static void style(Chip chip, boolean selected) {
Context ctx = chip.getContext();
float d = ctx.getResources().getDisplayMetrics().density;
chip.setCheckable(true);
chip.setChecked(selected);
chip.setCheckedIconVisible(true); // the "check" cue the design calls for
// A bare new Chip(ctx) picks up the theme's default (Assist) chipStyle, which has NO checkedIcon,
// so set a plain check explicitly -- otherwise setCheckedIconVisible shows nothing.
chip.setCheckedIcon(ContextCompat.getDrawable(ctx, R.drawable.ic_check_16));
chip.setChipCornerRadius(8 * d); // 8dp corner = chip/toggle (not a stadium pill)
chip.setChipMinHeight(32 * d); // the 32dp step on the 4dp role ladder
chip.setEnsureMinTouchTargetSize(true); // keep a >=48dp touch target on the 32dp chip
chip.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelLarge);

int teal = ContextCompat.getColor(ctx, R.color.k2go_teal);
int onTeal = ContextCompat.getColor(ctx, R.color.k2go_on_teal);
int[][] states = { new int[]{ android.R.attr.state_checked }, new int[0] };
chip.setChipBackgroundColor(new ColorStateList(states, new int[]{ teal, Color.TRANSPARENT }));
chip.setChipStrokeColor(new ColorStateList(states, new int[]{ Color.TRANSPARENT, teal }));
chip.setChipStrokeWidth(Math.max(1, Math.round(1.4f * d)));
chip.setTextColor(new ColorStateList(states, new int[]{ onTeal, teal }));
chip.setCheckedIconTint(ColorStateList.valueOf(onTeal));
}

/**
* Wire a horizontally-scrollable filter-chip row so it never reads as "the last chip": a soft fade
* at the scrolled edges (right while there is more to the right, left once scrolled), and on
* (re)build a scroll that reveals the selected chip with its neighbours peeking instead of pinned
* flush to an edge. Call after (re)populating the row. K2GO-385 (design k2go-chip-scroll-affordance-v1).
*/
public static void revealSelected(HorizontalScrollView scroll, ViewGroup row) {
float d = scroll.getResources().getDisplayMetrics().density;
scroll.setHorizontalFadingEdgeEnabled(true);
// 34dp (a touch longer than the default) so the edge fade stays legible for low vision, not so
// faint it can be missed when a chip happens to end right at the row edge.
scroll.setFadingEdgeLength(Math.round(34 * d));
scroll.post(() -> {
for (int i = 0; i < row.getChildCount(); i++) {
View c = row.getChildAt(i);
if (c instanceof Chip && ((Chip) c).isChecked()) {
int peek = c.getWidth(); // keep one chip of context so the active chip is not pinned
scroll.smoothScrollTo(Math.max(0, c.getLeft() - peek), 0);
return;
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* ============================================================================
* Name : SegmentedTabs.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : K2GO-385 (pill roles Q6, light variant). The Connect / Clone mode strips stay a
* two-segment teal strip (active = teal pill + on-teal text), but read as a
* single-select radio group to accessibility services: each tab is announced as a
* checkable RadioButton, checked when active, and marked selected. One definition
* instead of the identical paintTab that lived in both ConnectFragment and
* CloneFragment. Touch ripple comes from android:foreground on the tab in the layout.
* ============================================================================
*/
package org.appdevforall.k2go.redesign;

import android.view.View;
import android.widget.TextView;

import androidx.core.content.ContextCompat;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;

import org.appdevforall.k2go.R;

public final class SegmentedTabs {

private SegmentedTabs() {}

/** Paint one segment of a two-way mode strip and expose it as a checked/unchecked radio. */
public static void paint(TextView tab, boolean checked) {
tab.setBackgroundResource(checked ? R.drawable.k2go_primary_bg : 0);
tab.setTextColor(ContextCompat.getColor(tab.getContext(),
checked ? R.color.k2go_on_teal : R.color.k2go_muted));
tab.setSelected(checked);
ViewCompat.setAccessibilityDelegate(tab, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName("android.widget.RadioButton");
info.setCheckable(true);
info.setChecked(host.isSelected());
}
});
}
}
Loading
Loading