Commit bc2dc6fb authored by Lückemeyer's avatar Lückemeyer
Browse files

Gemini enhanced stats fragment

parent 9b3438fb
......@@ -24,13 +24,33 @@ public class StatsFragment extends Fragment {
/** true = all-time volet table (toggle right), false = by-day (toggle left) */
private boolean voletAllTime = true;
@FunctionalInterface
interface HeaderClickListener {
void onHeaderClick(int col);
}
// ── Simple display-ready row holder ──────────────────────────────────────
/** A fully-formatted table row, ready to hand to the UI thread. */
private static class DisplayRow {
final String[] cells;
DisplayRow(String... cells) { this.cells = cells; }
final Comparable[] keys;
DisplayRow(Comparable[] keys, String... cells) {
this.keys = keys;
this.cells = cells;
}
}
private List<DisplayRow> cacheVoletAllTime = new ArrayList<>();
private List<DisplayRow> cacheVoletByDay = new ArrayList<>();
private List<DisplayRow> cacheDay = new ArrayList<>();
private int sortVoletAllTimeCol = 4; // Rate
private boolean sortVoletAllTimeAsc = false;
private int sortVoletByDayCol = 5; // Rate
private boolean sortVoletByDayAsc = false;
private int sortDayCol = 0; // Date
private boolean sortDayAsc = false;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
......@@ -67,17 +87,19 @@ public class StatsFragment extends Fragment {
: String.format("%.0f%%", 100.0 * s.correct / s.total);
int streak = longestStreak(
db.tryDao().getCorrectSequenceForDaySync(s.day));
double ratio = s.total == 0 ? 0 : (double) s.correct / s.total;
dayRows.add(new DisplayRow(
new Comparable[]{s.day, ratio, ratio, streak},
s.day,
String.valueOf(s.correct),
String.valueOf(s.total),
s.correct + "/" + s.total,
rate,
String.valueOf(streak)));
}
cacheDay = dayRows;
requireActivity().runOnUiThread(() -> {
binding.progressStats.setVisibility(View.GONE);
renderDayTable(dayRows);
renderDayTable(cacheDay);
});
// Also kick off the volet table on the same background thread
......@@ -93,40 +115,44 @@ public class StatsFragment extends Fragment {
/** Must only be called from the executor thread. */
private void loadVoletTableOnBackground() {
final List<DisplayRow> rows;
final boolean allTime = voletAllTime;
final List<DisplayRow> rows = new ArrayList<>();
if (allTime) {
List<TryDao.VoletStat> stats = repo.getVoletStats();
rows = new ArrayList<>();
for (TryDao.VoletStat s : stats) {
String rate = s.total == 0 ? "—"
: String.format("%.0f%%", 100.0 * s.correct / s.total);
int streak = longestStreak(
db.tryDao().getCorrectSequenceForVoletSync(s.voletId));
double ratio = s.total == 0 ? 0 : (double) s.correct / s.total;
rows.add(new DisplayRow(
new Comparable[]{s.bookTitle, s.unitTitle, s.voletTitle, ratio, ratio, streak},
s.bookTitle, s.unitTitle, s.voletTitle,
s.correct + "/" + s.total, rate,
String.valueOf(streak)));
}
cacheVoletAllTime = rows;
} else {
List<TryDao.VoletDayStat> stats = repo.getVoletDayStats();
rows = new ArrayList<>();
for (TryDao.VoletDayStat s : stats) {
String rate = s.total == 0 ? "—"
: String.format("%.0f%%", 100.0 * s.correct / s.total);
int streak = longestStreak(
db.tryDao().getCorrectSequenceForVoletDaySync(s.voletId, s.day));
double ratio = s.total == 0 ? 0 : (double) s.correct / s.total;
rows.add(new DisplayRow(
s.day, s.voletTitle,
new Comparable[]{s.day, s.bookTitle, s.unitTitle, s.voletTitle, ratio, ratio, streak},
s.day, s.bookTitle, s.unitTitle, s.voletTitle,
s.correct + "/" + s.total, rate,
String.valueOf(streak), ""));
String.valueOf(streak)));
}
cacheVoletByDay = rows;
}
requireActivity().runOnUiThread(() -> {
binding.progressStats.setVisibility(View.GONE);
renderVoletTable(allTime, rows);
renderVoletTable(allTime, allTime ? cacheVoletAllTime : cacheVoletByDay);
});
}
......@@ -135,32 +161,94 @@ public class StatsFragment extends Fragment {
private void renderVoletTable(boolean allTime, List<DisplayRow> rows) {
TableLayout table = binding.tableVolet;
table.removeAllViews();
int col = allTime ? sortVoletAllTimeCol : sortVoletByDayCol;
boolean asc = allTime ? sortVoletAllTimeAsc : sortVoletByDayAsc;
sortRows(rows, col, asc);
if (allTime) {
table.addView(makeRow(true,
table.addView(makeRow(this::onVoletHeaderClick,
"Book", "Unit", "Volet", "Correct", "Rate", "Streak"));
if (rows.isEmpty()) {
table.addView(makeRow(false, "—", "—", "No data yet", "—", "—", "—"));
table.addView(makeRow(null, "—", "—", "No data yet", "—", "—", "—"));
}
} else {
table.addView(makeRow(true,
"Day", "Volet", "Correct", "Rate", "Streak", ""));
table.addView(makeRow(this::onVoletHeaderClick,
"Day", "Book", "Unit", "Volet", "Correct", "Rate", "Streak"));
if (rows.isEmpty()) {
table.addView(makeRow(false, "—", "—", "No data yet", "—", "—", ""));
table.addView(makeRow(null, "—", "—", "—", "No data yet", "—", "—", ""));
}
}
for (DisplayRow r : rows) table.addView(makeRow(false, r.cells));
for (DisplayRow r : rows) table.addView(makeRow(null, r.cells));
}
private void renderDayTable(List<DisplayRow> rows) {
TableLayout table = binding.tableDay;
table.removeAllViews();
table.addView(makeRow(true, "Date", "Correct", "Total", "Rate", "Streak"));
sortRows(rows, sortDayCol, sortDayAsc);
table.addView(makeRow(this::onDayHeaderClick, "Date", "Correct", "Rate", "Streak"));
if (rows.isEmpty()) {
table.addView(makeRow(false, "—", "—", "No data yet", "—", "—"));
table.addView(makeRow(null, "—", "—", "No data yet", "—"));
}
for (DisplayRow r : rows) table.addView(makeRow(null, r.cells));
}
// ── Table Sorting Helpers ─────────────────────────────────────────────────
private void sortRows(List<DisplayRow> rows, int col, boolean asc) {
if (rows.isEmpty()) return;
rows.sort((a, b) -> {
Comparable k1 = a.keys[col];
Comparable k2 = b.keys[col];
int cmp;
if (k1 == null && k2 == null) cmp = 0;
else if (k1 == null) cmp = -1;
else if (k2 == null) cmp = 1;
else {
@SuppressWarnings("unchecked")
int c = k1.compareTo(k2);
cmp = c;
}
return asc ? cmp : -cmp;
});
}
private void onVoletHeaderClick(int col) {
if (voletAllTime) {
if (sortVoletAllTimeCol == col) {
sortVoletAllTimeAsc = !sortVoletAllTimeAsc;
} else {
sortVoletAllTimeCol = col;
sortVoletAllTimeAsc = !isVoletAllTimeDescFirst(col);
}
renderVoletTable(true, cacheVoletAllTime);
} else {
if (sortVoletByDayCol == col) {
sortVoletByDayAsc = !sortVoletByDayAsc;
} else {
sortVoletByDayCol = col;
sortVoletByDayAsc = !isVoletByDayDescFirst(col);
}
renderVoletTable(false, cacheVoletByDay);
}
}
private void onDayHeaderClick(int col) {
if (sortDayCol == col) {
sortDayAsc = !sortDayAsc;
} else {
sortDayCol = col;
sortDayAsc = !isDayDescFirst(col);
}
for (DisplayRow r : rows) table.addView(makeRow(false, r.cells));
renderDayTable(cacheDay);
}
private boolean isVoletAllTimeDescFirst(int col) { return col == 4 || col == 5; }
private boolean isVoletByDayDescFirst(int col) { return col == 5 || col == 6; }
private boolean isDayDescFirst(int col) { return col == 2 || col == 3; }
// ── Streak computation ────────────────────────────────────────────────────
/** Longest consecutive run of correct answers (value=1) in the ordered sequence. */
......@@ -175,16 +263,19 @@ public class StatsFragment extends Fragment {
// ── Row builder ───────────────────────────────────────────────────────────
private TableRow makeRow(boolean header, String... cells) {
private TableRow makeRow(HeaderClickListener listener, String... cells) {
boolean header = (listener != null);
TableRow row = new TableRow(requireContext());
if (header) row.setBackgroundColor(0xFF6200EE);
for (String cell : cells) {
for (int i = 0; i < cells.length; i++) {
TextView tv = new TextView(requireContext());
tv.setText(cell);
tv.setText(cells[i]);
tv.setPadding(16, 10, 16, 10);
if (header) {
tv.setTextColor(0xFFFFFFFF);
tv.setTypeface(null, android.graphics.Typeface.BOLD);
final int col = i;
tv.setOnClickListener(v -> listener.onHeaderClick(col));
}
TableRow.LayoutParams lp = new TableRow.LayoutParams(
0, TableRow.LayoutParams.WRAP_CONTENT, 1f);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment