mirror of
https://github.com/avinal/nikki.git
synced 2026-07-04 05:50:10 +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 authRepository: AuthRepository by lazy { AuthRepository(apiClient, tokenStore) }
|
||||||
val memoRepository: MemoRepository by lazy { MemoRepository(apiClient, database.memoDao()) }
|
val memoRepository: MemoRepository by lazy { MemoRepository(apiClient, database.memoDao()) }
|
||||||
|
|
||||||
|
private var initJob: kotlinx.coroutines.Job? = null
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
initJob?.cancel()
|
||||||
|
initJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
launch { tokenStore.accessToken.collect { cachedToken = it } }
|
launch { tokenStore.accessToken.collect { cachedToken = it } }
|
||||||
launch { tokenStore.serverUrl.collect { cachedServerUrl = it } }
|
launch { tokenStore.serverUrl.collect { cachedServerUrl = it } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ object HttpClientFactory {
|
|||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
install(io.ktor.client.plugins.HttpTimeout) {
|
||||||
|
requestTimeoutMillis = 30_000
|
||||||
|
connectTimeoutMillis = 15_000
|
||||||
|
socketTimeoutMillis = 30_000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.plugin(HttpSend).intercept { request ->
|
client.plugin(HttpSend).intercept { request ->
|
||||||
|
|||||||
@@ -141,7 +141,13 @@ class MemoRepository(
|
|||||||
suspend fun reactToMemo(memoId: String, emoji: String): ApiResult<Unit> {
|
suspend fun reactToMemo(memoId: String, emoji: String): ApiResult<Unit> {
|
||||||
return when (apiClient.upsertReaction(memoId, emoji)) {
|
return when (apiClient.upsertReaction(memoId, emoji)) {
|
||||||
is ApiResult.Success -> {
|
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)
|
ApiResult.Success(Unit)
|
||||||
}
|
}
|
||||||
is ApiResult.Error -> ApiResult.Error(0, "Failed to react")
|
is ApiResult.Error -> ApiResult.Error(0, "Failed to react")
|
||||||
|
|||||||
@@ -276,7 +276,9 @@ private fun ExplorerPage(
|
|||||||
|
|
||||||
Spacer(Modifier.height(8.dp))
|
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) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
dayLabels.forEach { day ->
|
dayLabels.forEach { day ->
|
||||||
Text(day, fontSize = 11.sp, color = subtleColor.copy(alpha = 0.5f), modifier = Modifier.width(28.dp), textAlign = TextAlign.Center)
|
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))
|
Spacer(Modifier.height(4.dp))
|
||||||
|
|
||||||
val firstDayOfWeek = remember(calYear, calMonthIdx) {
|
val firstDayOfWeek = remember(calYear, calMonthIdx, weekStartDay) {
|
||||||
val month = calMonthIdx + 1
|
val month = calMonthIdx + 1
|
||||||
val y = if (month <= 2) calYear - 1 else calYear
|
val y = if (month <= 2) calYear - 1 else calYear
|
||||||
val m = if (month <= 2) month + 12 else month
|
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 h = (1 + (13 * (m + 1)) / 5 + y + y / 4 - y / 100 + y / 400) % 7
|
||||||
val mondayBased = ((h + 5) % 7)
|
val adjusted = ((h + 6 - weekStartDay) % 7)
|
||||||
if (mondayBased < 0) mondayBased + 7 else mondayBased
|
if (adjusted < 0) adjusted + 7 else adjusted
|
||||||
}
|
}
|
||||||
|
|
||||||
val cells = buildList {
|
val cells = buildList {
|
||||||
|
|||||||
@@ -37,18 +37,11 @@ class MemoDetailViewModel(
|
|||||||
|
|
||||||
fun toggleTask(lineIndex: Int, checked: Boolean) {
|
fun toggleTask(lineIndex: Int, checked: Boolean) {
|
||||||
val current = memo.value ?: return
|
val current = memo.value ?: return
|
||||||
val lines = current.content.lines().toMutableList()
|
val tasks = com.avinal.memos.parser.TaskParser.extractTasks(memoId, current.content)
|
||||||
if (lineIndex !in lines.indices) return
|
val task = tasks.find { it.lineIndex == lineIndex } ?: return
|
||||||
|
|
||||||
val line = lines[lineIndex]
|
|
||||||
lines[lineIndex] = if (checked) {
|
|
||||||
line.replaceFirst("- [ ]", "- [x]")
|
|
||||||
} else {
|
|
||||||
line.replaceFirst("- [x]", "- [ ]").replaceFirst("- [X]", "- [ ]")
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
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
|
val subtleColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
|
||||||
var composeText by remember { mutableStateOf("") }
|
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 showVisibilityPicker by remember { mutableStateOf(false) }
|
||||||
var uploadedAttachmentNames by remember { mutableStateOf<List<String>>(emptyList()) }
|
var uploadedAttachmentNames by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||||
var isUploading by remember { mutableStateOf(false) }
|
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) {
|
fun toggleTask(memoId: String, lineIndex: Int, checked: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val memo = memoRepository.getMemo(memoId) ?: return@launch
|
val memo = memoRepository.getMemo(memoId) ?: return@launch
|
||||||
val lines = memo.content.lines().toMutableList()
|
val tasks = com.avinal.memos.parser.TaskParser.extractTasks(memoId, memo.content)
|
||||||
if (lineIndex !in lines.indices) return@launch
|
val task = tasks.find { it.lineIndex == lineIndex } ?: return@launch
|
||||||
val line = lines[lineIndex]
|
val newContent = com.avinal.memos.parser.TaskParser.toggleTaskInContent(memo.content, task)
|
||||||
lines[lineIndex] = if (checked) {
|
if (newContent != memo.content) memoRepository.updateMemo(memoId, content = newContent)
|
||||||
line.replaceFirst("- [ ]", "- [x]")
|
|
||||||
} else {
|
|
||||||
line.replaceFirst("- [x]", "- [ ]").replaceFirst("- [X]", "- [ ]")
|
|
||||||
}
|
|
||||||
memoRepository.updateMemo(memoId, content = lines.joinToString("\n"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user