mirror of
https://github.com/avinal/nikki.git
synced 2026-07-03 21:40:09 +05:30
Fix 6 bugs: wire settings, dedup toggle, timeouts, leak, react
1. Default visibility now used in compose card (reads from DataStore) 2. Week start day wired to explorer calendar (rotates day labels, adjusts Zeller offset) 3. Task toggle deduplicated: both MemoListViewModel and MemoDetailViewModel now use TaskParser.toggleTaskInContent instead of manual string replacement 4. reactToMemo refetches single memo instead of all memos 5. AppDependencies stores Job reference, cancels before re-init 6. HTTP timeouts added: 30s request/socket, 15s connect 102 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
This commit is contained in:
@@ -39,8 +39,11 @@ class AppDependencies(
|
||||
val authRepository: AuthRepository by lazy { AuthRepository(apiClient, tokenStore) }
|
||||
val memoRepository: MemoRepository by lazy { MemoRepository(apiClient, database.memoDao()) }
|
||||
|
||||
private var initJob: kotlinx.coroutines.Job? = null
|
||||
|
||||
fun initialize() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
initJob?.cancel()
|
||||
initJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
launch { tokenStore.accessToken.collect { cachedToken = it } }
|
||||
launch { tokenStore.serverUrl.collect { cachedServerUrl = it } }
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ object HttpClientFactory {
|
||||
encodeDefaults = true
|
||||
})
|
||||
}
|
||||
install(io.ktor.client.plugins.HttpTimeout) {
|
||||
requestTimeoutMillis = 30_000
|
||||
connectTimeoutMillis = 15_000
|
||||
socketTimeoutMillis = 30_000
|
||||
}
|
||||
}
|
||||
|
||||
client.plugin(HttpSend).intercept { request ->
|
||||
|
||||
@@ -141,7 +141,13 @@ class MemoRepository(
|
||||
suspend fun reactToMemo(memoId: String, emoji: String): ApiResult<Unit> {
|
||||
return when (apiClient.upsertReaction(memoId, emoji)) {
|
||||
is ApiResult.Success -> {
|
||||
refreshMemos()
|
||||
when (val memoResult = apiClient.getMemo(memoId)) {
|
||||
is ApiResult.Success -> {
|
||||
val memo = memoResult.data.toDomain()
|
||||
memoDao.upsert(memo.toEntity(nowMillis()))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
ApiResult.Success(Unit)
|
||||
}
|
||||
is ApiResult.Error -> ApiResult.Error(0, "Failed to react")
|
||||
|
||||
@@ -276,7 +276,9 @@ private fun ExplorerPage(
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
val dayLabels = listOf("m", "t", "w", "t", "f", "s", "s")
|
||||
val weekStartDay by deps.tokenStore.weekStartDay.collectAsState(initial = 0)
|
||||
val baseDays = listOf("s", "m", "t", "w", "t", "f", "s")
|
||||
val dayLabels = baseDays.drop(weekStartDay) + baseDays.take(weekStartDay)
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
dayLabels.forEach { day ->
|
||||
Text(day, fontSize = 11.sp, color = subtleColor.copy(alpha = 0.5f), modifier = Modifier.width(28.dp), textAlign = TextAlign.Center)
|
||||
@@ -284,13 +286,13 @@ private fun ExplorerPage(
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
|
||||
val firstDayOfWeek = remember(calYear, calMonthIdx) {
|
||||
val firstDayOfWeek = remember(calYear, calMonthIdx, weekStartDay) {
|
||||
val month = calMonthIdx + 1
|
||||
val y = if (month <= 2) calYear - 1 else calYear
|
||||
val m = if (month <= 2) month + 12 else month
|
||||
val h = (1 + (13 * (m + 1)) / 5 + y + y / 4 - y / 100 + y / 400) % 7
|
||||
val mondayBased = ((h + 5) % 7)
|
||||
if (mondayBased < 0) mondayBased + 7 else mondayBased
|
||||
val adjusted = ((h + 6 - weekStartDay) % 7)
|
||||
if (adjusted < 0) adjusted + 7 else adjusted
|
||||
}
|
||||
|
||||
val cells = buildList {
|
||||
|
||||
@@ -37,18 +37,11 @@ class MemoDetailViewModel(
|
||||
|
||||
fun toggleTask(lineIndex: Int, checked: Boolean) {
|
||||
val current = memo.value ?: return
|
||||
val lines = current.content.lines().toMutableList()
|
||||
if (lineIndex !in lines.indices) return
|
||||
|
||||
val line = lines[lineIndex]
|
||||
lines[lineIndex] = if (checked) {
|
||||
line.replaceFirst("- [ ]", "- [x]")
|
||||
} else {
|
||||
line.replaceFirst("- [x]", "- [ ]").replaceFirst("- [X]", "- [ ]")
|
||||
}
|
||||
|
||||
val tasks = com.avinal.memos.parser.TaskParser.extractTasks(memoId, current.content)
|
||||
val task = tasks.find { it.lineIndex == lineIndex } ?: return
|
||||
viewModelScope.launch {
|
||||
memoRepository.updateMemo(memoId, content = lines.joinToString("\n"))
|
||||
val newContent = com.avinal.memos.parser.TaskParser.toggleTaskInContent(current.content, task)
|
||||
if (newContent != current.content) memoRepository.updateMemo(memoId, content = newContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,10 @@ fun MemoListScreen(
|
||||
val subtleColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
|
||||
var composeText by remember { mutableStateOf("") }
|
||||
var composeVisibility by remember { mutableStateOf(MemoVisibility.PRIVATE) }
|
||||
val defaultVis by produceState(MemoVisibility.PRIVATE) {
|
||||
deps.tokenStore.defaultVisibility.first().let { value = MemoVisibility.fromApiString(it) }
|
||||
}
|
||||
var composeVisibility by remember(defaultVis) { mutableStateOf(defaultVis) }
|
||||
var showVisibilityPicker by remember { mutableStateOf(false) }
|
||||
var uploadedAttachmentNames by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||
var isUploading by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -106,15 +106,10 @@ class MemoListViewModel(private val memoRepository: MemoRepository) : ViewModel(
|
||||
fun toggleTask(memoId: String, lineIndex: Int, checked: Boolean) {
|
||||
viewModelScope.launch {
|
||||
val memo = memoRepository.getMemo(memoId) ?: return@launch
|
||||
val lines = memo.content.lines().toMutableList()
|
||||
if (lineIndex !in lines.indices) return@launch
|
||||
val line = lines[lineIndex]
|
||||
lines[lineIndex] = if (checked) {
|
||||
line.replaceFirst("- [ ]", "- [x]")
|
||||
} else {
|
||||
line.replaceFirst("- [x]", "- [ ]").replaceFirst("- [X]", "- [ ]")
|
||||
}
|
||||
memoRepository.updateMemo(memoId, content = lines.joinToString("\n"))
|
||||
val tasks = com.avinal.memos.parser.TaskParser.extractTasks(memoId, memo.content)
|
||||
val task = tasks.find { it.lineIndex == lineIndex } ?: return@launch
|
||||
val newContent = com.avinal.memos.parser.TaskParser.toggleTaskInContent(memo.content, task)
|
||||
if (newContent != memo.content) memoRepository.updateMemo(memoId, content = newContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user