Skip to content

Commit 8a5bfc1

Browse files
ADFA-4910: Books download confirmation screen
Add a review step between the Books landing and the add/download action, mirroring ZIM/Maps Confirm. The landing 'Review (N)' button hands the selection to a new BooksConfirmFragment (list of picks + total count + honest one-at-a-time note); the primary button banks to BooksWishlist in the wizard or starts BooksDownloadService live, then returns via the hub.
1 parent 6cb0237 commit 8a5bfc1

5 files changed

Lines changed: 283 additions & 21 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* ============================================================================
3+
* Name : BooksConfirmFragment.java
4+
* Author : AppDevForAll
5+
* Copyright : Copyright (c) 2026 AppDevForAll
6+
* Description : ADFA-4910. Books Confirm — a review step between the Books landing and the
7+
* download/bank action, mirroring ZimConfirmFragment. Lists the picked books
8+
* (title + author), a total count, an honest note, and a primary button. Books
9+
* carry no per-item size, so this shows a count (not GB). In the wizard the button
10+
* banks the picks to BooksWishlist and returns to the Get More hub; live, it hands
11+
* the picks to BooksDownloadService and opens the downloads screen. Reads the
12+
* selection cart from SetupLibraryActivity.
13+
* ============================================================================
14+
*/
15+
package org.iiab.controller.redesign;
16+
17+
import android.graphics.Typeface;
18+
import android.os.Bundle;
19+
import android.view.Gravity;
20+
import android.view.LayoutInflater;
21+
import android.view.View;
22+
import android.view.ViewGroup;
23+
import android.widget.Button;
24+
import android.widget.LinearLayout;
25+
import android.widget.TextView;
26+
27+
import androidx.annotation.NonNull;
28+
import androidx.annotation.Nullable;
29+
import androidx.core.content.ContextCompat;
30+
import androidx.fragment.app.Fragment;
31+
32+
import java.util.ArrayList;
33+
import java.util.LinkedHashMap;
34+
import java.util.List;
35+
import java.util.Map;
36+
37+
import org.iiab.controller.R;
38+
39+
public class BooksConfirmFragment extends Fragment {
40+
41+
private int px(int dp) { return Math.round(dp * getResources().getDisplayMetrics().density); }
42+
43+
/** Selection cart from the activity: gutenberg_id -> {title, author, download_url}. */
44+
private LinkedHashMap<String, String[]> cart() {
45+
return (getActivity() instanceof SetupLibraryActivity)
46+
? ((SetupLibraryActivity) getActivity()).getBooksCart() : new LinkedHashMap<>();
47+
}
48+
49+
@Nullable
50+
@Override
51+
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle s) {
52+
View root = inflater.inflate(R.layout.fragment_k2go_books_confirm, container, false);
53+
54+
TextView back = root.findViewById(R.id.k2go_bconf_back);
55+
back.setText("‹ " + getString(R.string.k2go_books_back_title));
56+
back.setOnClickListener(v -> requireActivity().getSupportFragmentManager().popBackStack());
57+
58+
LinkedHashMap<String, String[]> cart = cart();
59+
LinearLayout box = root.findViewById(R.id.k2go_bconf_list);
60+
for (Map.Entry<String, String[]> e : cart.entrySet()) {
61+
String[] v = e.getValue();
62+
String title = v != null && v.length > 0 ? v[0] : "";
63+
String author = v != null && v.length > 1 ? v[1] : "";
64+
box.addView(row(title, author, false));
65+
box.addView(divider());
66+
}
67+
box.addView(row(getString(R.string.k2go_books_total_fmt, cart.size()), "", true));
68+
69+
boolean wiz = (getActivity() instanceof SetupLibraryActivity) && ((SetupLibraryActivity) getActivity()).isBooksWizard();
70+
Button add = root.findViewById(R.id.k2go_bconf_add);
71+
add.setText(getString(wiz ? R.string.k2go_books_add_setup_fmt : R.string.k2go_books_add_fmt, cart.size()));
72+
add.setEnabled(!cart.isEmpty());
73+
add.setOnClickListener(v -> {
74+
if (!(getActivity() instanceof SetupLibraryActivity)) return;
75+
SetupLibraryActivity a = (SetupLibraryActivity) getActivity();
76+
if (a.isBooksWizard()) a.booksWizardConfirm(); // pre-install: bank the selection
77+
else a.startBooksDownload(); // live: download now
78+
});
79+
80+
return root;
81+
}
82+
83+
private View row(String name, String sub, boolean totalRow) {
84+
LinearLayout r = new LinearLayout(requireContext());
85+
r.setOrientation(LinearLayout.HORIZONTAL);
86+
r.setGravity(Gravity.CENTER_VERTICAL);
87+
r.setPadding(0, px(10), 0, px(10));
88+
89+
LinearLayout text = new LinearLayout(requireContext());
90+
text.setOrientation(LinearLayout.VERTICAL);
91+
TextView n = new TextView(requireContext());
92+
n.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
93+
n.setText(name);
94+
n.setTextColor(ContextCompat.getColor(requireContext(), totalRow ? R.color.k2go_teal : R.color.k2go_ink));
95+
if (totalRow) n.setTypeface(n.getTypeface(), Typeface.BOLD);
96+
text.addView(n);
97+
if (!sub.isEmpty()) {
98+
TextView subv = new TextView(requireContext());
99+
subv.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
100+
subv.setText(sub);
101+
subv.setTextColor(ContextCompat.getColor(requireContext(), R.color.k2go_muted));
102+
text.addView(subv);
103+
}
104+
r.addView(text, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
105+
return r;
106+
}
107+
108+
private View divider() {
109+
View d = new View(requireContext());
110+
d.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1));
111+
d.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.k2go_hairline));
112+
return d;
113+
}
114+
}

‎controller/app/src/main/java/org/iiab/controller/redesign/BooksLandingFragment.java‎

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
116116
});
117117

118118
buildChips();
119-
addBtn.setOnClickListener(v -> startDownloads());
119+
addBtn.setOnClickListener(v -> reviewSelection());
120120
downloadsLink.setOnClickListener(v -> openDownloads());
121121

122122
loadLanguages();
@@ -391,36 +391,29 @@ private void refreshFooter() {
391391
addBtn.setVisibility(View.VISIBLE);
392392
int n = selected.size();
393393
addBtn.setEnabled(n > 0);
394-
int fmt = wizard ? R.string.k2go_books_add_setup_fmt : R.string.k2go_books_add_fmt;
395-
addBtn.setText(n > 0 ? getString(fmt, n) : getString(R.string.k2go_books_add_none));
394+
// ADFA-4910: the landing now leads to a Confirm/review step, not a direct add.
395+
addBtn.setText(n > 0 ? getString(R.string.k2go_books_review_fmt, n) : getString(R.string.k2go_books_add_none));
396396
}
397397
if (wizard) { downloadsLink.setVisibility(View.GONE); return; } // no live downloads pre-install
398398
boolean active = BooksDownloadService.hasSession();
399399
downloadsLink.setVisibility(active ? View.VISIBLE : View.GONE);
400400
if (active) downloadsLink.setText(getString(R.string.k2go_books_view_downloads));
401401
}
402402

403-
private void startDownloads() {
403+
/** ADFA-4910: hand the current selection to the activity cart and open the Confirm/review
404+
* screen. Confirm is the single place that banks (wizard) or downloads (live), so both flows
405+
* share one review step. The picks stay in {@code selected} so returning here keeps the state. */
406+
private void reviewSelection() {
404407
if (selected.isEmpty()) return;
405-
if (wizard) { // pre-install: record the picks into the wishlist; provisioned after install
406-
for (JSONObject b : selected.values()) {
407-
BooksWishlist.add(requireContext(), b.optString("gutenberg_id", ""),
408-
b.optString("title", ""), b.optString("download_url", ""));
409-
}
410-
selected.clear();
411-
render(); // picked books now show the "Added" band
412-
return;
413-
}
414-
List<String> ids = new ArrayList<>(), titles = new ArrayList<>(), urls = new ArrayList<>();
408+
if (!(getActivity() instanceof SetupLibraryActivity)) return;
409+
LinkedHashMap<String, String[]> cart = new LinkedHashMap<>();
415410
for (JSONObject b : selected.values()) {
416-
ids.add(b.optString("gutenberg_id", ""));
417-
titles.add(b.optString("title", ""));
418-
urls.add(b.optString("download_url", ""));
411+
cart.put(b.optString("gutenberg_id", ""), new String[]{
412+
b.optString("title", ""), b.optString("author", ""), b.optString("download_url", "")});
419413
}
420-
BooksDownloadService.start(requireContext().getApplicationContext(),
421-
ids.toArray(new String[0]), titles.toArray(new String[0]), urls.toArray(new String[0]));
422-
selected.clear();
423-
openDownloads();
414+
SetupLibraryActivity a = (SetupLibraryActivity) getActivity();
415+
a.setBooksCart(cart);
416+
a.openBooksConfirm();
424417
}
425418

426419
private void openDownloads() {

‎controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public class SetupLibraryActivity extends AppCompatActivity {
4444
// banks the per-layer selection to MapsWishlist instead of starting a live runrole.
4545
private boolean mapsWizard = false;
4646

47+
// ADFA-4910: true while the Books flow runs inside the wizard (pre-install). The Confirm step then
48+
// banks the picks to BooksWishlist instead of starting a live download.
49+
private boolean booksWizard = false;
50+
51+
// ADFA-4910: the Books selection handed from the landing to the Confirm screen:
52+
// gutenberg_id -> {title, author, download_url}.
53+
private final java.util.LinkedHashMap<String, String[]> booksCart = new java.util.LinkedHashMap<>();
54+
4755
@Override
4856
protected void onCreate(Bundle savedInstanceState) {
4957
super.onCreate(savedInstanceState);
@@ -123,6 +131,7 @@ private InstallationPlanner.Tier readInstalledTier() {
123131
public void openContentType(String key, String title) {
124132
zimWizard = false; // live (post-install) path; the ZIM terminal downloads, not wishlists
125133
mapsWizard = false; // ADFA-4900: live (post-install) path; Maps installs, not wishlists
134+
booksWizard = false; // ADFA-4910: live (post-install) path; Books download, not wishlists
126135
androidx.fragment.app.Fragment f;
127136
if ("maps".equals(key)) f = new MapsLandingFragment();
128137
else if ("wikipedia".equals(key)) f = new ZimLandingFragment(); // Wikipedia & ZIM content
@@ -226,12 +235,61 @@ public void zimWizardConfirm() {
226235

227236
/** ADFA-4853: open Books in wizard mode (pre-install, offline catalog -> wishlist). */
228237
public void openBooksWizard() {
238+
booksWizard = true;
229239
getSupportFragmentManager().beginTransaction()
230240
.replace(R.id.k2go_setup_host, BooksLandingFragment.newInstance(true))
231241
.addToBackStack("wizard_books")
232242
.commit();
233243
}
234244

245+
public boolean isBooksWizard() { return booksWizard; }
246+
247+
/** ADFA-4910: the Books selection cart (gutenberg_id -> {title, author, download_url}), set by
248+
* the landing when the user taps "Review" and read by BooksConfirmFragment. */
249+
public java.util.LinkedHashMap<String, String[]> getBooksCart() { return booksCart; }
250+
public void setBooksCart(java.util.LinkedHashMap<String, String[]> picks) {
251+
booksCart.clear();
252+
if (picks != null) booksCart.putAll(picks);
253+
}
254+
255+
/** ADFA-4910: Books landing "Review" -> Confirm (list + total + honest note). */
256+
public void openBooksConfirm() {
257+
getSupportFragmentManager().beginTransaction()
258+
.replace(R.id.k2go_setup_host, new BooksConfirmFragment())
259+
.addToBackStack("books_confirm")
260+
.commit();
261+
}
262+
263+
/** ADFA-4910: Books Confirm terminal in wizard mode — bank the picks and return to the hub. */
264+
public void booksWizardConfirm() {
265+
for (java.util.Map.Entry<String, String[]> e : booksCart.entrySet()) {
266+
String[] v = e.getValue();
267+
String title = v != null && v.length > 0 ? v[0] : "";
268+
String url = v != null && v.length > 2 ? v[2] : "";
269+
BooksWishlist.add(this, e.getKey(), title, url);
270+
}
271+
booksCart.clear();
272+
getSupportFragmentManager().popBackStack("wizard_books",
273+
androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE);
274+
}
275+
276+
/** ADFA-4910: Books Confirm terminal in live mode — hand the picks to the download service and
277+
* open the downloads screen (per-book checklist + retry). */
278+
public void startBooksDownload() {
279+
java.util.List<String> ids = new java.util.ArrayList<>(), titles = new java.util.ArrayList<>(),
280+
urls = new java.util.ArrayList<>();
281+
for (java.util.Map.Entry<String, String[]> e : booksCart.entrySet()) {
282+
String[] v = e.getValue();
283+
ids.add(e.getKey());
284+
titles.add(v != null && v.length > 0 ? v[0] : "");
285+
urls.add(v != null && v.length > 2 ? v[2] : "");
286+
}
287+
booksCart.clear();
288+
BooksDownloadService.start(getApplicationContext(),
289+
ids.toArray(new String[0]), titles.toArray(new String[0]), urls.toArray(new String[0]));
290+
openBooksDownloads();
291+
}
292+
235293
/** ADFA-4850: Books landing -> the download manager screen (per-book checklist + retry). */
236294
public void openBooksDownloads() {
237295
getSupportFragmentManager().beginTransaction()
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- ADFA-4910: Books Confirm. A review step between the Books landing and the download/bank
3+
action, mirroring ZIM/Maps Confirm: list of picked books (title + author), a total count,
4+
an honest "one at a time" note, and the primary button. Books carry no per-item size, so
5+
this shows a count, not GB. Button text/behaviour depends on wizard (bank) vs live (download). -->
6+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
7+
android:layout_width="match_parent"
8+
android:layout_height="match_parent"
9+
android:fillViewport="true">
10+
11+
<LinearLayout
12+
android:layout_width="match_parent"
13+
android:layout_height="wrap_content"
14+
android:orientation="vertical"
15+
android:paddingLeft="20dp"
16+
android:paddingRight="20dp"
17+
android:paddingTop="20dp"
18+
android:paddingBottom="24dp">
19+
20+
<TextView
21+
android:id="@+id/k2go_bconf_back"
22+
android:layout_width="wrap_content"
23+
android:layout_height="wrap_content"
24+
android:padding="4dp"
25+
android:textAppearance="?attr/textAppearanceBodyMedium"
26+
android:textColor="@color/k2go_teal" />
27+
28+
<TextView
29+
android:layout_width="match_parent"
30+
android:layout_height="wrap_content"
31+
android:layout_marginTop="4dp"
32+
android:text="@string/k2go_books_confirm_title"
33+
android:textAppearance="?attr/textAppearanceHeadlineMedium"
34+
android:textColor="@color/k2go_ink" />
35+
36+
<TextView
37+
android:layout_width="match_parent"
38+
android:layout_height="wrap_content"
39+
android:layout_marginTop="2dp"
40+
android:text="@string/k2go_books_confirm_sub"
41+
android:textAppearance="?attr/textAppearanceBodyMedium"
42+
android:textColor="@color/k2go_muted" />
43+
44+
<LinearLayout
45+
android:id="@+id/k2go_bconf_list"
46+
android:layout_width="match_parent"
47+
android:layout_height="wrap_content"
48+
android:layout_marginTop="16dp"
49+
android:orientation="vertical"
50+
android:paddingLeft="16dp"
51+
android:paddingRight="16dp"
52+
android:paddingTop="4dp"
53+
android:paddingBottom="4dp"
54+
android:background="@drawable/k2go_card_bg" />
55+
56+
<LinearLayout
57+
android:layout_width="match_parent"
58+
android:layout_height="wrap_content"
59+
android:layout_marginTop="12dp"
60+
android:orientation="vertical"
61+
android:padding="14dp"
62+
android:background="@drawable/k2go_info_bg">
63+
64+
<TextView
65+
android:layout_width="match_parent"
66+
android:layout_height="wrap_content"
67+
android:text="@string/k2go_books_confirm_note_title"
68+
android:textStyle="bold"
69+
android:textAppearance="?attr/textAppearanceTitleSmall"
70+
android:textColor="@color/k2go_teal" />
71+
72+
<TextView
73+
android:layout_width="match_parent"
74+
android:layout_height="wrap_content"
75+
android:layout_marginTop="4dp"
76+
android:text="@string/k2go_books_dl_sub"
77+
android:textAppearance="?attr/textAppearanceBodySmall"
78+
android:textColor="@color/k2go_teal" />
79+
</LinearLayout>
80+
81+
<Button
82+
android:id="@+id/k2go_bconf_add"
83+
android:layout_width="match_parent"
84+
android:layout_height="52dp"
85+
android:layout_marginTop="16dp"
86+
android:textColor="@android:color/white"
87+
android:textAllCaps="false"
88+
android:backgroundTint="@color/k2go_teal" />
89+
</LinearLayout>
90+
</ScrollView>

‎controller/app/src/main/res/values/strings_k2go.xml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,13 @@
493493
<string name="k2go_books_added" translatable="false">Added</string>
494494
<string name="k2go_books_add_none" translatable="false">Select books to add</string>
495495
<string name="k2go_books_view_downloads" translatable="false">View downloads</string>
496+
<!-- ADFA-4910: Books Confirm (review step between landing and download/bank). -->
497+
<string name="k2go_books_review_fmt" translatable="false">Review selection (%1$d)</string>
498+
<string name="k2go_books_back_title" translatable="false">Books</string>
499+
<string name="k2go_books_confirm_title" translatable="false">Ready to add these books?</string>
500+
<string name="k2go_books_confirm_sub" translatable="false">Your selection.</string>
501+
<string name="k2go_books_confirm_note_title" translatable="false">One at a time</string>
502+
<string name="k2go_books_total_fmt" translatable="false">%1$d books</string>
496503
<string name="k2go_books_dl_title" translatable="false">Downloads</string>
497504
<string name="k2go_books_dl_sub" translatable="false">Added one at a time — kind to Project Gutenberg.</string>
498505
<string name="k2go_books_dl_detail_fmt" translatable="false">%1$d of %2$d books</string>

0 commit comments

Comments
 (0)