Skip to content

Commit 67f002e

Browse files
feat(android): add HOT CHAPTERS section to MY READING
reading_events already tracks (book, chapter, timestamp) per view, so BibleDatabase.getHotChapters() just groups one level finer than the existing getMostReadBooks() — same "including repeats" semantics, top 10 individual chapters by view count. Rows are tappable: reuses the existing deep-link mechanism (viewModel.pendingDeepLink + navigate("bible")) that DeepLink.kt/App Links already drive, so tapping a hot chapter jumps straight to it instead of just reporting the number. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e7e40f0 commit 67f002e

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleDatabase.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,24 @@ class BibleDatabase(private val context: Context) {
376376
return list
377377
}
378378

379+
/**
380+
* The individual chapters (not whole books) with the highest view
381+
* count, most-viewed first -- same "including repeats" semantics as
382+
* getMostReadBooks(), just grouped one level finer.
383+
*/
384+
fun getHotChapters(limit: Int = 10): List<HotChapter> {
385+
if (!exists()) return emptyList()
386+
val list = mutableListOf<HotChapter>()
387+
val db = open()
388+
val cursor = db.rawQuery(
389+
"SELECT book, chapter, COUNT(*) as cnt FROM reading_events GROUP BY book, chapter ORDER BY cnt DESC LIMIT ?",
390+
arrayOf(limit.toString())
391+
)
392+
while (cursor.moveToNext()) list.add(HotChapter(cursor.getString(0), cursor.getInt(1), cursor.getInt(2)))
393+
cursor.close(); db.close()
394+
return list
395+
}
396+
379397
/**
380398
* Count of chapter-views at or after `sinceMillis`. Callers compute
381399
* rolling-window cutoffs (e.g. now - 7d) rather than this method doing
@@ -584,3 +602,6 @@ data class InterlinearWordWithVerse(
584602
val verse: Int, val wordIndex: Int,
585603
val original: String, val translation: String, val strongs: String
586604
)
605+
606+
/** One row of BibleDatabase.getHotChapters() -- a single chapter's view count. */
607+
data class HotChapter(val book: String, val chapter: Int, val views: Int)

mobile/app/src/main/java/com/bytecats/metanoia/bible/BibleManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class BibleManager(private val context: Context) {
7777
fun recordChapterRead(book: String, chapter: Int) = db.recordChapterRead(book, chapter)
7878
fun getReadCompletion(): Map<String, Float> = db.getReadCompletion()
7979
fun getMostReadBooks(limit: Int = 5): List<Pair<String, Int>> = db.getMostReadBooks(limit)
80+
fun getHotChapters(limit: Int = 10): List<HotChapter> = db.getHotChapters(limit)
8081
fun getReadingEventCounts(sinceMillis: Long): Int = db.getReadingEventCounts(sinceMillis)
8182
fun getFirstEverReadTimestamp(): Long? = db.getFirstEverReadTimestamp()
8283
fun getReadEpochDaysDescending(): List<Long> = db.getReadEpochDaysDescending()

mobile/app/src/main/java/com/bytecats/metanoia/ui/screens/ReadingAnalyticsScreen.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.bytecats.metanoia.ui.screens
22

33
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
45
import androidx.compose.foundation.layout.*
56
import androidx.compose.foundation.rememberScrollState
67
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -25,6 +26,7 @@ import androidx.compose.ui.text.style.TextAlign
2526
import androidx.compose.ui.unit.dp
2627
import androidx.navigation.NavController
2728
import com.bytecats.metanoia.bible.ReadingStats
29+
import com.bytecats.metanoia.bible.VerseReference
2830
import com.bytecats.metanoia.models.BOOKS
2931
import com.bytecats.metanoia.ui.components.ConfirmActionDialog
3032
import com.bytecats.metanoia.ui.components.StatItemCompact
@@ -64,6 +66,7 @@ fun ReadingAnalyticsScreen(navController: NavController, viewModel: MainViewMode
6466
var showResetDialog by remember { mutableStateOf(false) }
6567

6668
val mostRead = remember(resetNonce) { bible.getMostReadBooks(5) }
69+
val hotChapters = remember(resetNonce) { bible.getHotChapters(10) }
6770
val now = remember(resetNonce) { System.currentTimeMillis() }
6871
val weekCount = remember(resetNonce) { bible.getReadingEventCounts(ReadingStats.weekCutoff(now)) }
6972
val monthCount = remember(resetNonce) { bible.getReadingEventCounts(ReadingStats.monthCutoff(now)) }
@@ -252,6 +255,44 @@ fun ReadingAnalyticsScreen(navController: NavController, viewModel: MainViewMode
252255
}
253256
}
254257
}
258+
Text("HOT CHAPTERS", style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary)
259+
Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f))) {
260+
if (hotChapters.isEmpty()) {
261+
Text(
262+
"Nothing read yet -- open a chapter to start tracking.",
263+
modifier = Modifier.padding(24.dp),
264+
color = MaterialTheme.colorScheme.outline
265+
)
266+
} else {
267+
Column(modifier = Modifier.padding(vertical = 8.dp)) {
268+
hotChapters.forEachIndexed { idx, hot ->
269+
Row(
270+
modifier = Modifier
271+
.fillMaxWidth()
272+
.clickable {
273+
viewModel.pendingDeepLink = VerseReference(hot.book, hot.chapter, null)
274+
navController.navigate("bible")
275+
}
276+
.padding(horizontal = 24.dp, vertical = 10.dp),
277+
horizontalArrangement = Arrangement.SpaceBetween,
278+
verticalAlignment = Alignment.CenterVertically
279+
) {
280+
Row(verticalAlignment = Alignment.CenterVertically) {
281+
Text(
282+
"${idx + 1}",
283+
modifier = Modifier.width(24.dp),
284+
color = MaterialTheme.colorScheme.outline,
285+
fontWeight = FontWeight.Bold
286+
)
287+
Text("${hot.book} ${hot.chapter}", fontWeight = FontWeight.Bold)
288+
}
289+
Text("${hot.views} view${if (hot.views == 1) "" else "s"}", color = TokyoAmber)
290+
}
291+
if (idx < hotChapters.lastIndex) HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f))
292+
}
293+
}
294+
}
295+
}
255296
Spacer(modifier = Modifier.height(8.dp))
256297
}
257298
}

0 commit comments

Comments
 (0)