The unified inbox query (WHERE folder = ?, no accountId) has no folder-leading index, so it scans in timestamp order and materializes the whole unified inbox (~4k rows at a 20k cache) into memory on every emission. Apply Paging 3 to the unified browse path so query, mapping, and recomposition cost scale with the visible window, not the total cache. - MessageDao.pagingUnifiedFolderSummaries: a PagingSource over the folder's synced rows (inInbox = 1); unified search keeps the whole-folder query so it can still surface transient server-search hits. - MailRepository.pagedUnifiedFolderMessages: a Pager (pageSize 40, initialLoad 120, no placeholders) mapping summaries to domain. - MailboxViewModel.pagedMessages: paged while browsing the unified inbox, else empty; the messages list flow stays empty in that state so the whole cache is never materialized. Selection captures each row's accountId at tap time, so "Move" still resolves the selection's account without an in-memory list. - MailboxScreen renders the unified browse list via collectAsLazyPagingItems; per-account and search views render the flat list unchanged (issue #86 stays flat). Profiling (docs/perf/issue-124-unified-inbox-paging.md) on an api29 emulator: current whole-inbox first-emit ~24.6 ms at a 20k cache vs. the paged first page ~6.8 ms and flat regardless of cache size (~3.6x). EXPLAIN QUERY PLAN shows the paged query still stops early on the existing timestamp index, so no (folder, ...) index and no schema migration are added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
122 lines
7.4 KiB
TOML
122 lines
7.4 KiB
TOML
[versions]
|
|
agp = "9.2.0"
|
|
kotlin = "2.4.0"
|
|
ksp = "2.3.9"
|
|
hilt = "2.60"
|
|
hiltNavigationCompose = "1.3.0"
|
|
coreKtx = "1.17.0"
|
|
lifecycle = "2.9.4"
|
|
activityCompose = "1.12.4"
|
|
navigationCompose = "2.9.8"
|
|
webkit = "1.12.1"
|
|
biometric = "1.1.0"
|
|
composeBom = "2026.06.00"
|
|
room = "2.8.4"
|
|
paging = "3.3.6"
|
|
sqlcipher = "4.16.0"
|
|
datastore = "1.2.1"
|
|
work = "2.11.2"
|
|
coroutines = "1.10.2"
|
|
# Forced above androidx.savedstate's transitive 1.7.3 (shared with the androidTest classpath via
|
|
# AGP 9 consistent resolution); Room's schema-bundle serializers need >= 1.8.0 or MigrationTestHelper
|
|
# throws AbstractMethodError on GeneratedSerializer.typeParametersSerializers().
|
|
kotlinxSerialization = "1.8.1"
|
|
appauth = "0.11.1"
|
|
angusMail = "2.0.5"
|
|
junit = "4.13.2"
|
|
androidxJunit = "1.2.1"
|
|
espresso = "3.7.0"
|
|
turbine = "1.2.1"
|
|
mockk = "1.14.11"
|
|
greenmail = "2.1.9"
|
|
errorprone = "2.50.0"
|
|
# Lint/format. ktlint owns formatting; detekt owns static analysis (formatting ruleset off).
|
|
# detekt 2.0 is the only line with Gradle 9 support (stable 1.23.x tops out at Gradle 8.12).
|
|
ktlint = "14.2.0"
|
|
detekt = "2.0.0-alpha.5"
|
|
|
|
[libraries]
|
|
# AndroidX core / lifecycle / activity / navigation
|
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
|
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
|
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }
|
|
# Biometric / device-credential auth for the opt-in app-lock + encrypted-cache unlock.
|
|
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
|
|
|
|
# Compose (versions managed by the BOM)
|
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
|
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
|
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
|
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
|
|
|
|
# Hilt (DI)
|
|
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
|
# Pulled onto the compile classpath because Hilt/Dagger generated code references it.
|
|
error-prone-annotations = { group = "com.google.errorprone", name = "error_prone_annotations", version.ref = "errorprone" }
|
|
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
|
|
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
|
androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltNavigationCompose" }
|
|
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltNavigationCompose" }
|
|
|
|
# Room (local cache)
|
|
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
|
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
|
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
|
# Room MigrationTestHelper (instrumented migration tests).
|
|
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
|
|
# Room PagingSource support — lets @Query methods return androidx.paging.PagingSource.
|
|
androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" }
|
|
# SQLCipher — opt-in at-rest encryption of the Room cache.
|
|
sqlcipher-android = { group = "net.zetetic", name = "sqlcipher-android", version.ref = "sqlcipher" }
|
|
|
|
# Paging 3 — pages the unified "All inboxes" list so its query/recomposition cost scales with the
|
|
# visible window, not the whole cache (issue #124). runtime = Pager/PagingData; compose =
|
|
# collectAsLazyPagingItems; testing = asSnapshot for JVM unit tests.
|
|
androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" }
|
|
androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
|
|
androidx-paging-testing = { group = "androidx.paging", name = "paging-testing", version.ref = "paging" }
|
|
|
|
# DataStore (settings) / WorkManager (sync) — wired in later increments
|
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
|
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
|
|
|
|
# Coroutines
|
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
|
|
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
|
|
|
|
# Serialization BOM — imported as a platform (not used directly) to raise the transitive
|
|
# kotlinx-serialization runtime to the version Room's schema bundles were compiled against.
|
|
kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-bom", version.ref = "kotlinxSerialization" }
|
|
|
|
# Email transport / OAuth — wired in later increments
|
|
appauth = { group = "net.openid", name = "appauth", version.ref = "appauth" }
|
|
angus-mail = { group = "org.eclipse.angus", name = "angus-mail", version.ref = "angusMail" }
|
|
|
|
# Test
|
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }
|
|
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
|
|
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
|
|
greenmail = { group = "com.icegreen", name = "greenmail", version.ref = "greenmail" }
|
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
|
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
|
|
androidx-espresso-intents = { group = "androidx.test.espresso", name = "espresso-intents", version.ref = "espresso" }
|
|
|
|
[plugins]
|
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
|
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
|
|
detekt = { id = "dev.detekt", version.ref = "detekt" }
|