Этап 1: баги данных (test-first) — ICS-парсер, QR-пароль, курсор почты
Заведена тестовая инфраструктура (core:network junit) + 12 unit-тестов. Починено:
- ICS parseIcsInstant: суффикс Z → UTC (был локальным!), TZID учитывается, floating→локаль
- parseProps сохраняет параметры: несколько ATTENDEE не схлопываются, CN/PARTSTAT/ROLE
читаются (была порча участников при редактировании события → PUT на сервер)
- unescape single-pass (последовательные replace ломались на экранированном бэкслеше)
- CalDavClient.parseIcsInstant делегирует в CalendarIcs — устранён рассинхрон календарь↔задачи
- LoginFlowClient: маркер-парсер вместо split('&') — QR-пароль с &/: не теряется
- MailRepository/ViewModel/Models/Cache/Screen: курсор пагинации Int→Long (обрезка ~2038)
Проверено: 12 тестов зелёные (CalendarIcsTest 8 + LoginFlowClientTest 4) + assembleRelease OK.
This commit is contained in:
@@ -150,7 +150,8 @@ class MailCacheRepository(context: Context) {
|
||||
private fun parseFolderPage(json: JSONObject): MailMessagesPage =
|
||||
MailMessagesPage(
|
||||
messages = parseMessages(json.optJSONArray("messages")),
|
||||
nextCursor = json.opt("nextCursor").takeUnless { it == JSONObject.NULL } as? Int,
|
||||
// org.json может вернуть число как Integer или Long — берём через Number.toLong().
|
||||
nextCursor = (json.opt("nextCursor").takeUnless { it == JSONObject.NULL } as? Number)?.toLong(),
|
||||
)
|
||||
|
||||
private fun messageDetailToJson(detail: MailMessageDetail): JSONObject =
|
||||
|
||||
@@ -266,7 +266,8 @@ data class MailBootstrap(
|
||||
|
||||
data class MailMessagesPage(
|
||||
val messages: List<MailMessage>,
|
||||
val nextCursor: Int?,
|
||||
// Long: курсор = dateInt (Unix-секунды); Int резал бы значение (баг, ломается ~2038).
|
||||
val nextCursor: Long?,
|
||||
)
|
||||
|
||||
data class MailAppSettings(
|
||||
|
||||
@@ -38,7 +38,7 @@ class MailRepository {
|
||||
folder: MailFolderEntry,
|
||||
searchQuery: String = "",
|
||||
searchParams: MailSearchParams = MailSearchParams(),
|
||||
cursor: Int? = null,
|
||||
cursor: Long? = null,
|
||||
limit: Int = PAGE_SIZE,
|
||||
): MailMessagesPage {
|
||||
val client = authedClient(session)
|
||||
@@ -51,10 +51,10 @@ class MailRepository {
|
||||
return pageFromMessages(messages, limit)
|
||||
}
|
||||
|
||||
private fun pageFromMessages(messages: List<MailMessage>, limit: Int): MailMessagesPage {
|
||||
// API cursor is sent_at (dateInt), not database message id.
|
||||
internal fun pageFromMessages(messages: List<MailMessage>, limit: Int): MailMessagesPage {
|
||||
// API cursor is sent_at (dateInt, Unix-секунды) — Long, без обрезки в Int.
|
||||
val nextCursor = if (messages.size >= limit) {
|
||||
messages.lastOrNull()?.dateInt?.takeIf { it > 0 }?.toInt()
|
||||
messages.lastOrNull()?.dateInt?.takeIf { it > 0 }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ private fun MailInboxScreen(
|
||||
isOutbox: Boolean,
|
||||
messages: List<MailMessage>,
|
||||
outboxMessages: List<MailOutboxMessage>,
|
||||
nextCursor: Int?,
|
||||
nextCursor: Long?,
|
||||
onSearchChange: (String) -> Unit,
|
||||
onQuickFilterToggle: (MailQuickFilter) -> Unit,
|
||||
onComposeClick: () -> Unit,
|
||||
|
||||
@@ -25,7 +25,7 @@ data class MailUiState(
|
||||
val folders: List<MailFolderEntry> = emptyList(),
|
||||
val selectedFolder: MailFolderEntry? = null,
|
||||
val messages: List<MailMessage> = emptyList(),
|
||||
val nextCursor: Int? = null,
|
||||
val nextCursor: Long? = null,
|
||||
val searchQuery: String = "",
|
||||
val searchParams: MailSearchParams = MailSearchParams(),
|
||||
val collapsedAccountIds: Set<Int> = emptySet(),
|
||||
@@ -1005,7 +1005,7 @@ class MailViewModel(
|
||||
if (page.messages.size < MailRepository.PAGE_SIZE) {
|
||||
return page.copy(nextCursor = null)
|
||||
}
|
||||
val cursor = page.messages.lastOrNull()?.dateInt?.takeIf { it > 0 }?.toInt()
|
||||
val cursor = page.messages.lastOrNull()?.dateInt?.takeIf { it > 0 }
|
||||
return page.copy(nextCursor = cursor)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user