2020 * building them through a ContextThemeWrapper(Theme.K2Go) — a Material3.DayNight theme
2121 * carrying the app palette — without touching PortalActivity's global theme. Colors come
2222 * from theme attributes (colorSurface/onSurface/primary/error/…) so they follow light/dark;
23- * dialogs use MaterialAlertDialogBuilder, buttons MaterialButton, and the progress bars
24- * the Material progress indicators. All user-facing text lives in string resources.
23+ * confirm/error dialogs use the shared BrandDialog (K2GO-385), buttons MaterialButton, and
24+ * the progress bars the Material progress indicators. All user-facing text lives in string
25+ * resources.
2526 *
2627 * ADFA-5062: in the operation model (ADR-5061) this FQR region fetch is
2728 * Operation.content("maps") — a CONTENT / LIVE operation. Unlike the banked ContentType
5556
5657import com .google .android .material .button .MaterialButton ;
5758import com .google .android .material .color .MaterialColors ;
58- import com .google .android .material .dialog .MaterialAlertDialogBuilder ;
5959import com .google .android .material .progressindicator .CircularProgressIndicator ;
6060import com .google .android .material .progressindicator .LinearProgressIndicator ;
6161
6262import org .appdevforall .k2go .R ;
63+ import org .appdevforall .k2go .ui .dialog .BrandDialog ;
6364import org .appdevforall .k2go .util .M3Text ;
6465import org .json .JSONArray ;
6566import org .json .JSONObject ;
@@ -81,6 +82,9 @@ public final class FqrController {
8182 // theme: build every view/dialog through this wrapper so ?attr colors + the app font resolve.
8283 private final Context themed ;
8384 private final int cSurface , cSurfaceContainer , cSurfaceHighest , cOnSurface , cOnSurfaceVariant , cPrimary , cError ;
85+ // K2GO-385 (C1): state colours for the download bar -- amber = stopped (halted, not live), leaf = done.
86+ // Fixed tokens (same in day/night), so a halted or finished job reads the same on either theme.
87+ private final int cAmber , cLeaf ;
8488
8589 private volatile boolean active = false ; // written on UI thread, read on the WebView binder thread
8690 private AlertDialog dialog ; // "calculating" / consent (one at a time)
@@ -132,6 +136,8 @@ public FqrController(Activity activity, WebView webView) {
132136 // Material3-specific surface roles above; read them from the appcompat namespace.
133137 this .cPrimary = attr (androidx .appcompat .R .attr .colorPrimary , 0xFF4CAF7D );
134138 this .cError = attr (androidx .appcompat .R .attr .colorError , 0xFFE05353 );
139+ this .cAmber = androidx .core .content .ContextCompat .getColor (themed , R .color .k2go_amber );
140+ this .cLeaf = androidx .core .content .ContextCompat .getColor (themed , R .color .k2go_leaf );
135141 }
136142
137143 private int attr (int attrId , int fallback ) {
@@ -235,11 +241,7 @@ private void handleExtract(String name, String box) {
235241 @ Override public void onError (String message ) {
236242 if (estimateCanceled ) return ;
237243 dismissDialog ();
238- new MaterialAlertDialogBuilder (themed )
239- .setTitle (R .string .k2go_fqr_estimate_error_title )
240- .setMessage (message )
241- .setPositiveButton (android .R .string .ok , null )
242- .show ();
244+ errorDialog (R .string .k2go_fqr_estimate_error_title , message );
243245 }
244246 });
245247 }
@@ -270,7 +272,9 @@ private void resetMapSelection() {
270272 // ---- Consent -----------------------------------------------------------------------------
271273 private void showCalculating () {
272274 dismissDialog ();
273- LinearLayout row = dialogContent (dp (24 )); // ADFA-5027: M3 dialog inset (4dp grid)
275+ // hpad 0: BrandDialog's content host already sits at the card's horizontal inset (double-inset
276+ // otherwise). Vertical 8dp keeps the 4dp grid (ADFA-5027).
277+ LinearLayout row = dialogContent (0 , dp (8 ));
274278 row .setOrientation (LinearLayout .HORIZONTAL );
275279 row .setGravity (Gravity .CENTER_VERTICAL );
276280 CircularProgressIndicator spin = new CircularProgressIndicator (themed );
@@ -285,17 +289,18 @@ private void showCalculating() {
285289 row .addView (t );
286290 // ADFA-5043: canceling while estimating must drop the pending estimate AND clear the map's
287291 // selection — otherwise the crosshair lingers and a late estimate could still pop the consent.
288- dialog = new MaterialAlertDialogBuilder (themed )
289- .setView (row )
292+ dialog = new BrandDialog (themed )
293+ .setContentView (row )
290294 .setCancelable (true )
291- .setOnCancelListener (d -> { estimateCanceled = true ; resetMapSelection (); })
292- .show ();
295+ .setOnCancel (() -> { estimateCanceled = true ; resetMapSelection (); })
296+ .show ()
297+ .getDialog ();
293298 }
294299
295300 private void showConsent (String name , String box , long transfer , long archive , long free , long freeAfter ) {
296- // ADFA-5027: M3 dialog spacing — content aligned to the title (24dp) with 4dp-grid vertical
297- // breathing room, so it isn't cramped against the edges/ title/buttons .
298- LinearLayout body = dialogContent (dp ( 24 ) , dp (8 ));
301+ // ADFA-5027: 4dp-grid vertical breathing room. hpad 0: BrandDialog's content host already
302+ // carries the card's horizontal inset (aligned to the title), so no inset is added here .
303+ LinearLayout body = dialogContent (0 , dp (8 ));
299304
300305 TextView sub = new TextView (themed );
301306 sub .setText (str (R .string .k2go_fqr_consent_sub , name , human (transfer ), human (archive )));
@@ -337,17 +342,18 @@ private void showConsent(String name, String box, long transfer, long archive, l
337342 body .addView (warn );
338343 }
339344
340- dialog = new MaterialAlertDialogBuilder (themed )
345+ // ADFA-5043: bailing out here (Not now / tap-outside) must also clear the map's FQR selection
346+ // + tool -- the same reset the name dialog's Cancel does -- or the crosshair and the drawn area
347+ // linger with a stale "download this region" button on top.
348+ dialog = new BrandDialog (themed )
341349 .setTitle (R .string .k2go_fqr_consent_title )
342- .setView (body )
343- // ADFA-5043: bailing out here (Not now / tap-outside) must also clear the map's FQR
344- // selection + tool — the same reset the name dialog's Cancel does — or the crosshair and
345- // the drawn area linger with a stale "download this region" button on top.
346- .setNegativeButton (R .string .k2go_fqr_not_now , (d , w ) -> { d .dismiss (); resetMapSelection (); })
347- .setPositiveButton (R .string .k2go_fqr_download , (d , w ) -> startDownload (name ))
348- .setOnCancelListener (d -> resetMapSelection ())
350+ .setContentView (body )
351+ .setPositive (R .string .k2go_fqr_download , () -> startDownload (name ))
352+ .setNegative (R .string .k2go_fqr_not_now , () -> resetMapSelection ())
353+ .setOnCancel (() -> resetMapSelection ())
349354 .setCancelable (true )
350- .show ();
355+ .show ()
356+ .getDialog ();
351357
352358 // Kick off the actual download only after consent; the box already re-validates.
353359 // (startDownload is invoked from the positive button above.)
@@ -364,9 +370,10 @@ private void startDownload(String name) {
364370 showOverlay (name , pendingArchive );
365371 client .download (name , box , new MapsRegionClient .DownloadListener () {
366372 @ Override public void onProgress (int percent , long speed ) {
367- if (overlayStopped ) { // retried/running again: back to Stop
373+ if (overlayStopped ) { // retried/running again: back to Stop, live teal bar
368374 overlayStopped = false ;
369375 if (overlayStop != null ) overlayStop .setText (R .string .k2go_clone_stop_confirm );
376+ setBarState (cPrimary , true );
370377 }
371378 if (overlayStop != null ) overlayStop .setEnabled (true ); // re-enable after a Stop/Retry tap
372379 updateOverlay (percent , speed );
@@ -381,18 +388,18 @@ private void startDownload(String name) {
381388 // #2: freeze the bar (determinate, no animation) so it doesn't keep animating under "Stopped".
382389 if (overlayBar != null && overlayBar .isIndeterminate ()) setBarMode (false );
383390 if (overlayBar != null && percent >= 0 ) overlayBar .setProgressCompat (percent , false );
391+ setBarState (cAmber , false ); // amber + dimmed: a halted job must not look live
384392 }
385393 @ Override public void onDone () {
394+ setBarState (cLeaf , true ); // done: a full leaf bar reads as success
386395 updateOverlay (100 , 0 );
387396 if (overlayTitle != null ) overlayTitle .setText (R .string .k2go_fqr_region_added ); // may be gone if hidden
388397 webView .postDelayed (() -> { hideOverlay (); webView .reload (); }, 1200 );
389398 }
390399 @ Override public void onError (String message ) {
391400 hideOverlay ();
392401 if (!"canceled" .equals (message )) {
393- new MaterialAlertDialogBuilder (themed )
394- .setTitle (R .string .k2go_fqr_download_failed ).setMessage (message )
395- .setPositiveButton (android .R .string .ok , null ).show ();
402+ errorDialog (R .string .k2go_fqr_download_failed , message );
396403 }
397404 }
398405 });
@@ -443,15 +450,18 @@ private void showOverlay(String name, long sizeBytes) {
443450 // ADFA-4896: Stop/Retry beside Cancel. The label follows the reported state; the tap fires the
444451 // matching verb and the poll (onPaused/onProgress) is the source of truth.
445452 overlayStopped = false ;
446- // K2GO-385 (PR3): the download controls use the app button system (K2Go outlined stadium) via the
447- // shared overlay, not a bare Material3 outlined button. FQR's overlay is a themed (day/night)
448- // surface, so the K2Go outlined style's theme teal is right here -- not the fixed boot tokens.
449- ContextThemeWrapper btnCtx = new ContextThemeWrapper (themed , R .style .ThemeOverlay_K2Go_Button_Outlined );
450- overlayStop = new MaterialButton (btnCtx , null );
453+ // K2GO-385: download-controls role ladder (design k2go-download-card-style-sizes-v1) -- the
454+ // keep-the-download primary (Stop/Retry) is the FILLED teal button; Cancel, which discards the
455+ // transfer, is a TEXT button in clay. Never two equal outline pills. FQR's overlay is a themed
456+ // (day/night) surface, so the app button styles' theme teal/clay are right here -- not the fixed
457+ // boot tokens. The role look lives only in the styles; no colour is set in Java.
458+ ContextThemeWrapper filledCtx = new ContextThemeWrapper (themed , R .style .ThemeOverlay_K2Go_Button_Filled );
459+ overlayStop = new MaterialButton (filledCtx , null );
451460 overlayStop .setText (R .string .k2go_clone_stop_confirm );
452461 overlayStop .setOnClickListener (v -> toggleStop ());
453462 row .addView (overlayStop );
454- MaterialButton cancel = new MaterialButton (btnCtx , null );
463+ ContextThemeWrapper textDangerCtx = new ContextThemeWrapper (themed , R .style .ThemeOverlay_K2Go_Button_Text_Destructive );
464+ MaterialButton cancel = new MaterialButton (textDangerCtx , null );
455465 cancel .setText (R .string .k2go_cancel );
456466 cancel .setOnClickListener (v -> { client .cancel (); hideOverlay (); });
457467 row .addView (cancel );
@@ -509,6 +519,14 @@ private void setBarMode(boolean indeterminate) {
509519 overlayBar .setVisibility (vis == View .GONE ? View .VISIBLE : vis );
510520 }
511521
522+ /** K2GO-385 (C1): colour the download bar by state and dim it when the job is not live, so a stopped
523+ * job never looks like a running one (teal = downloading, amber = stopped, leaf = done). */
524+ private void setBarState (int color , boolean live ) {
525+ if (overlayBar == null ) return ;
526+ overlayBar .setIndicatorColor (color );
527+ overlayBar .setAlpha (live ? 1f : 0.55f );
528+ }
529+
512530 /** bytes/sec -> "2.4 MB/s". Empty for non-positive (speed not reported yet). */
513531 private static String humanRate (long bps ) {
514532 if (bps <= 0 ) return "" ;
@@ -674,22 +692,25 @@ private void flyTo(Region r) {
674692 }
675693
676694 private void confirmDelete (String name ) {
677- new MaterialAlertDialogBuilder (themed )
695+ // K2GO-385: a destructive region delete uses the shared BrandDialog (clay outlined destructive +
696+ // text Cancel), like the other from-scratch/erase confirms, instead of a flat MaterialAlertDialog
697+ // with two equal text buttons. themed (not the host activity) carries Theme_K2Go, which the dialog
698+ // frame needs -- PortalActivity's own theme is not K2Go.
699+ new BrandDialog (themed )
678700 .setTitle (str (R .string .k2go_fqr_delete_confirm_title , name ))
679701 .setMessage (R .string .k2go_fqr_delete_confirm_msg )
680- .setNegativeButton (R .string .k2go_cancel , null )
681- .setPositiveButton (R .string .k2go_fqr_delete , (d , w ) -> client .deleteRegion (name , new MapsRegionClient .DeleteListener () {
702+ .setDestructive (R .string .k2go_fqr_delete , () -> client .deleteRegion (name , new MapsRegionClient .DeleteListener () {
682703 @ Override public void onOk () {
683704 toast (str (R .string .k2go_fqr_deleted , name ));
684705 highlight = null ;
685706 webView .reload (); // map redraws without the region
686707 refreshRegions (); // and the list drops it
687708 }
688709 @ Override public void onError (String m ) {
689- new MaterialAlertDialogBuilder (themed ).setTitle (R .string .k2go_fqr_delete_failed ).setMessage (m )
690- .setPositiveButton (android .R .string .ok , null ).show ();
710+ errorDialog (R .string .k2go_fqr_delete_failed , m );
691711 }
692712 }))
713+ .setNegative (R .string .k2go_cancel , null )
693714 .show ();
694715 }
695716
@@ -712,7 +733,17 @@ private void closeDeleteSheetAndTool() {
712733 }
713734
714735 // ---- helpers -----------------------------------------------------------------------------
715- /** Padded, transparent container for MaterialAlertDialog setView (the dialog paints the surface). */
736+ /** K2GO-385: a single-button (OK) error dialog on the shared BrandDialog, so the three FQR error
737+ * cases (estimate / download / delete) share one recipe. */
738+ private void errorDialog (int titleRes , CharSequence message ) {
739+ new BrandDialog (themed )
740+ .setTitle (titleRes )
741+ .setMessage (message )
742+ .setPositive (android .R .string .ok , (BrandDialog .OnClick ) null )
743+ .show ();
744+ }
745+
746+ /** Padded, transparent container for the dialog body (BrandDialog paints the surface). */
716747 private LinearLayout dialogContent (int pad ) { return dialogContent (pad , pad ); }
717748
718749 /** Vertical content holder with horizontal/vertical insets (4dp grid). */
0 commit comments