diff --git a/.gitignore b/.gitignore
index bedd239..0f3cc3d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@ local.properties
/kotlin-js-store
.kotlin/
composeApp/schemas/
+index.html
+script.js
+styles.css
diff --git a/.idea/artifacts/composeApp.xml b/.idea/artifacts/composeApp.xml
index 76c1070..c58b4f3 100644
--- a/.idea/artifacts/composeApp.xml
+++ b/.idea/artifacts/composeApp.xml
@@ -1,6 +1,8 @@
$PROJECT_DIR$/composeApp/build/libs
-
+
+
+
\ No newline at end of file
diff --git a/androidApp/proguard-rules.pro b/androidApp/proguard-rules.pro
index 992e329..f0b647c 100644
--- a/androidApp/proguard-rules.pro
+++ b/androidApp/proguard-rules.pro
@@ -5,11 +5,20 @@
# kotlinx.serialization
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt
--keepclassmembers class kotlinx.serialization.json.** { *** Companion; }
--keepclasseswithmembers class kotlinx.serialization.json.** { kotlinx.serialization.KSerializer serializer(...); }
--keep,includedescriptorclasses class com.avinal.memos.**$$serializer { *; }
--keepclassmembers class com.avinal.memos.** { *** Companion; }
--keepclasseswithmembers class com.avinal.memos.** { kotlinx.serialization.KSerializer serializer(...); }
+-keepclassmembers class kotlinx.serialization.json.** {
+ *** Companion;
+}
+-keepclasseswithmembers class kotlinx.serialization.json.** {
+ kotlinx.serialization.KSerializer serializer(...);
+}
+
+# Keep serializers generated for project model classes
+-keepclassmembers class com.avinal.memos.** {
+ *** Companion;
+}
+-keepclasseswithmembers class com.avinal.memos.** {
+ kotlinx.serialization.KSerializer serializer(...);
+}
# Room
-keep class * extends androidx.room.RoomDatabase
diff --git a/androidApp/src/main/res/values/colors.xml b/androidApp/src/main/res/values/colors.xml
index f8c6127..045e125 100644
--- a/androidApp/src/main/res/values/colors.xml
+++ b/androidApp/src/main/res/values/colors.xml
@@ -1,10 +1,3 @@
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
+
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/App.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/App.kt
index 69bc100..9b88416 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/App.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/App.kt
@@ -3,7 +3,6 @@ package com.avinal.memos
import androidx.compose.runtime.Composable
import coil3.ImageLoader
import coil3.compose.setSingletonImageLoaderFactory
-import coil3.compose.LocalPlatformContext
import coil3.network.ktor3.KtorNetworkFetcherFactory
import com.avinal.memos.ui.navigation.AppNavHost
import com.avinal.memos.ui.theme.MemosAppTheme
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt
index bd66666..9058ed9 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt
@@ -4,13 +4,11 @@ import com.avinal.memos.api.model.AttachmentDto
import com.avinal.memos.api.model.AttachmentRef
import com.avinal.memos.api.model.CreateAttachmentRequest
import com.avinal.memos.api.model.CreateMemoRequest
-import com.avinal.memos.api.model.FieldMask
import com.avinal.memos.api.model.ListMemosResponse
import com.avinal.memos.api.model.MemoDto
import com.avinal.memos.api.model.ReactionDto
import com.avinal.memos.api.model.UpsertReactionRequest
import com.avinal.memos.api.model.UpdateMemoBody
-import com.avinal.memos.api.model.UpdateMemoRequest
import com.avinal.memos.api.model.UserDto
import io.ktor.client.HttpClient
import io.ktor.client.call.body
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/Mappers.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/Mappers.kt
index d1d31b0..2ca7b25 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/Mappers.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/Mappers.kt
@@ -27,6 +27,7 @@ fun MemoDto.toDomain(): Memo = Memo(
snippet = snippet,
attachments = attachments.map { it.toDomain() },
reactions = reactions.map { it.toDomain() },
+ commentCount = relations.count { it.type == "COMMENT" && it.relatedMemo.name == name },
)
fun AttachmentDto.toDomain(): Attachment = Attachment(
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/MemoDto.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/MemoDto.kt
index 9ea0d7d..d35cd5c 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/MemoDto.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/model/MemoDto.kt
@@ -54,11 +54,17 @@ data class ReactionDto(
@Serializable
data class RelationDto(
- val memo: String = "",
- val relatedMemo: String = "",
+ val memo: RelationRefDto = RelationRefDto(),
+ val relatedMemo: RelationRefDto = RelationRefDto(),
val type: String = "",
)
+@Serializable
+data class RelationRefDto(
+ val name: String = "",
+ val snippet: String = "",
+)
+
@Serializable
data class ListMemosResponse(
val memos: List = emptyList(),
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/MemosDatabase.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/MemosDatabase.kt
index e08bea9..385ed44 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/MemosDatabase.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/MemosDatabase.kt
@@ -5,7 +5,7 @@ import androidx.room.RoomDatabase
import com.avinal.memos.db.dao.MemoDao
import com.avinal.memos.db.entity.MemoEntity
-@Database(entities = [MemoEntity::class], version = 3)
+@Database(entities = [MemoEntity::class], version = 4)
abstract class MemosDatabase : RoomDatabase() {
abstract fun memoDao(): MemoDao
}
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/EntityMappers.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/EntityMappers.kt
index ee5acf4..15aea2e 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/EntityMappers.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/EntityMappers.kt
@@ -48,6 +48,7 @@ fun MemoEntity.toDomain(): Memo = Memo(
snippet = snippet,
attachments = deserializeAttachments(attachmentsJson),
reactions = deserializeReactions(reactionsJson),
+ commentCount = commentCount,
)
fun Memo.toEntity(cachedAt: Long): MemoEntity = MemoEntity(
@@ -68,6 +69,7 @@ fun Memo.toEntity(cachedAt: Long): MemoEntity = MemoEntity(
snippet = snippet,
attachmentsJson = serializeAttachments(attachments),
reactionsJson = serializeReactions(reactions),
+ commentCount = commentCount,
cachedAt = cachedAt,
)
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/MemoEntity.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/MemoEntity.kt
index 05f1d55..44dbf0e 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/MemoEntity.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/db/entity/MemoEntity.kt
@@ -22,5 +22,6 @@ data class MemoEntity(
val snippet: String,
val attachmentsJson: String = "[]",
val reactionsJson: String = "[]",
+ val commentCount: Int = 0,
val cachedAt: Long,
)
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/domain/Memo.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/domain/Memo.kt
index 8a6a823..13c8ae2 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/domain/Memo.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/domain/Memo.kt
@@ -20,6 +20,7 @@ data class Memo(
val snippet: String,
val attachments: List = emptyList(),
val reactions: List = emptyList(),
+ val commentCount: Int = 0,
)
data class Attachment(
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MarkdownText.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MarkdownText.kt
index 0690fd7..6775aae 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MarkdownText.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MarkdownText.kt
@@ -24,7 +24,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MemoCard.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MemoCard.kt
index 6097487..969c49c 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MemoCard.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/components/MemoCard.kt
@@ -125,6 +125,10 @@ fun MemoCard(
Spacer(Modifier.width(8.dp))
}
Text(formatAbsoluteDate(memo.displayTime), fontSize = 12.sp, color = subtleColor)
+ if (memo.commentCount > 0) {
+ Spacer(Modifier.width(8.dp))
+ Text("${memo.commentCount} comment${if (memo.commentCount > 1) "s" else ""}", fontSize = 12.sp, color = subtleColor)
+ }
}
Spacer(Modifier.height(8.dp))
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MainScreen.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MainScreen.kt
index d182222..63bc120 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MainScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MainScreen.kt
@@ -341,17 +341,31 @@ private fun ExplorerPage(
}
if (allTags.isNotEmpty()) {
+ val tasksByTag = remember(memos) {
+ val parser = com.avinal.memos.parser.TaskParser
+ val allTasks = memos.flatMap { memo -> parser.extractTasks(memo.id, memo.content) }
+ allTasks.filter { !it.isCompleted }.groupBy { it.lists.firstOrNull() ?: "" }
+ }
+
Spacer(Modifier.height(20.dp))
Text("tags", fontSize = 19.sp, fontWeight = FontWeight.Light, color = textColor)
Spacer(Modifier.height(8.dp))
+
+ Row(modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)) {
+ Text("tag", fontSize = 11.sp, color = subtleColor.copy(alpha = 0.5f), modifier = Modifier.weight(1f))
+ Text("memos", fontSize = 11.sp, color = subtleColor.copy(alpha = 0.5f), modifier = Modifier.width(50.dp), textAlign = TextAlign.End)
+ Text("tasks", fontSize = 11.sp, color = subtleColor.copy(alpha = 0.5f), modifier = Modifier.width(50.dp), textAlign = TextAlign.End)
+ }
+
allTags.forEach { tag ->
- val count = memos.count { it.tags.contains(tag) }
+ val memoCount = memos.count { it.tags.contains(tag) }
+ val taskCount = tasksByTag[tag]?.size ?: 0
Row(
- modifier = Modifier.fillMaxWidth().clickable { onTagSelected(tag) }.padding(vertical = 6.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().clickable { onTagSelected(tag) }.padding(vertical = 5.dp),
) {
- Text("#$tag", fontSize = 14.sp, color = accent)
- Text("$count", fontSize = 12.sp, color = subtleColor)
+ Text("#$tag", fontSize = 14.sp, color = accent, modifier = Modifier.weight(1f))
+ Text("$memoCount", fontSize = 13.sp, color = subtleColor, modifier = Modifier.width(50.dp), textAlign = TextAlign.End)
+ Text("$taskCount", fontSize = 13.sp, color = if (taskCount > 0) accent else subtleColor, modifier = Modifier.width(50.dp), textAlign = TextAlign.End)
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MemoDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MemoDetailScreen.kt
index 38ecf04..4381516 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MemoDetailScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/memos/MemoDetailScreen.kt
@@ -12,45 +12,44 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.avinal.memos.AppDependencies
import com.avinal.memos.api.ApiResult
-import com.avinal.memos.domain.Memo
import com.avinal.memos.api.model.toDomain
+import com.avinal.memos.domain.Memo
import com.avinal.memos.ui.components.AttachmentGrid
import com.avinal.memos.ui.components.MarkdownText
import com.avinal.memos.ui.components.ReactionBar
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.input.ImeAction
-import kotlinx.coroutines.launch
import com.avinal.memos.ui.theme.LocalAccentColor
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
@Composable
fun MemoDetailScreen(
@@ -65,6 +64,8 @@ fun MemoDetailScreen(
val isLoading by viewModel.isLoading.collectAsState()
val serverUrl by produceState("") { value = deps.tokenStore.serverUrl.first() ?: "" }
val accent = LocalAccentColor.current
+ val textColor = MaterialTheme.colorScheme.onBackground
+ val subtleColor = MaterialTheme.colorScheme.onSurfaceVariant
Column(
modifier = Modifier
@@ -72,14 +73,14 @@ fun MemoDetailScreen(
.background(MaterialTheme.colorScheme.background)
.statusBarsPadding(),
) {
- Text(
- "← back",
- fontSize = 14.sp,
- color = accent,
- modifier = Modifier
- .clickable(onClick = onBack)
- .padding(start = 24.dp, top = 12.dp, bottom = 12.dp),
- )
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 12.dp, top = 12.dp, bottom = 8.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text("← back", fontSize = 14.sp, color = accent, modifier = Modifier.clickable(onClick = onBack))
+ Text("edit", fontSize = 14.sp, color = accent, modifier = Modifier.clickable(onClick = onEdit))
+ }
when {
isLoading && memo == null -> {
@@ -90,7 +91,7 @@ fun MemoDetailScreen(
memo == null -> {
Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Text("could not load memo", fontSize = 15.sp)
+ Text("could not load memo", fontSize = 15.sp, color = textColor)
Spacer(Modifier.height(8.dp))
Text("retry", fontSize = 14.sp, color = accent, modifier = Modifier.clickable { viewModel.retry() })
}
@@ -103,13 +104,15 @@ fun MemoDetailScreen(
.verticalScroll(rememberScrollState())
.padding(start = 24.dp, end = 12.dp),
) {
- Text(
- memo!!.visibility.name.lowercase(),
- fontSize = 12.sp,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- )
+ Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+ Text(memo!!.visibility.name.lowercase(), fontSize = 12.sp, color = subtleColor)
+ Text(formatDateTime(memo!!.createTime), fontSize = 12.sp, color = subtleColor)
+ if (memo!!.updateTime != memo!!.createTime) {
+ Text("edited ${formatDateTime(memo!!.updateTime)}", fontSize = 12.sp, color = subtleColor)
+ }
+ }
- Spacer(Modifier.height(8.dp))
+ Spacer(Modifier.height(10.dp))
MarkdownText(
markdown = memo!!.content,
@@ -127,7 +130,7 @@ fun MemoDetailScreen(
}
Spacer(Modifier.height(20.dp))
- CommentsSection(memoId = memoId, deps = deps, accent = accent)
+ CommentsSection(memoId = memoId, deps = deps, accent = accent, textColor = textColor, subtleColor = subtleColor)
Spacer(Modifier.height(24.dp))
}
@@ -136,14 +139,21 @@ fun MemoDetailScreen(
}
}
+private val monthNames = listOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
+
+private fun formatDateTime(instant: kotlin.time.Instant): String {
+ val local = instant.toLocalDateTime(TimeZone.currentSystemDefault())
+ return "${monthNames[local.month.ordinal]} ${local.day}, ${local.year}"
+}
+
@Composable
private fun CommentsSection(
memoId: String,
deps: AppDependencies,
accent: Color,
+ textColor: Color,
+ subtleColor: Color,
) {
- val textColor = MaterialTheme.colorScheme.onBackground
- val subtleColor = MaterialTheme.colorScheme.onSurfaceVariant
var comments by remember { mutableStateOf>(emptyList()) }
var commentText by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(true) }
@@ -158,6 +168,18 @@ private fun CommentsSection(
isLoading = false
}
+ fun submitComment() {
+ if (commentText.isBlank()) return
+ val text = commentText
+ commentText = ""
+ scope.launch {
+ when (val result = deps.apiClient.createComment(memoId, text)) {
+ is ApiResult.Success -> comments = comments + result.data.toDomain()
+ else -> {}
+ }
+ }
+ }
+
Column {
Text("comments", fontSize = 19.sp, fontWeight = FontWeight.Light, color = textColor)
@@ -170,7 +192,10 @@ private fun CommentsSection(
} else {
comments.forEach { comment ->
Column(modifier = Modifier.padding(bottom = 12.dp)) {
- Text(comment.creator, fontSize = 12.sp, color = subtleColor)
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ Text(comment.creator, fontSize = 12.sp, color = subtleColor)
+ Text(formatDateTime(comment.createTime), fontSize = 12.sp, color = subtleColor)
+ }
Spacer(Modifier.height(2.dp))
MarkdownText(markdown = comment.content)
}
@@ -179,10 +204,7 @@ private fun CommentsSection(
Spacer(Modifier.height(8.dp))
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
- ) {
+ Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
TextField(
value = commentText,
onValueChange = { commentText = it },
@@ -191,20 +213,7 @@ private fun CommentsSection(
singleLine = true,
textStyle = MaterialTheme.typography.bodyMedium.copy(color = textColor),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
- keyboardActions = KeyboardActions(onSend = {
- if (commentText.isNotBlank()) {
- val text = commentText
- commentText = ""
- scope.launch {
- when (val result = deps.apiClient.createComment(memoId, text)) {
- is ApiResult.Success -> {
- comments = comments + result.data.toDomain()
- }
- else -> {}
- }
- }
- }
- }),
+ keyboardActions = KeyboardActions(onSend = { submitComment() }),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
@@ -219,16 +228,7 @@ private fun CommentsSection(
fontWeight = FontWeight.SemiBold,
color = if (commentText.isNotBlank()) accent else subtleColor.copy(alpha = 0.3f),
modifier = Modifier
- .then(if (commentText.isNotBlank()) Modifier.clickable {
- val text = commentText
- commentText = ""
- scope.launch {
- when (val result = deps.apiClient.createComment(memoId, text)) {
- is ApiResult.Success -> { comments = comments + result.data.toDomain() }
- else -> {}
- }
- }
- } else Modifier)
+ .then(if (commentText.isNotBlank()) Modifier.clickable { submitComment() } else Modifier)
.padding(start = 8.dp),
)
}
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/tasks/TaskDetailSheet.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/tasks/TaskDetailSheet.kt
index 3844bc6..1ae29c8 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/tasks/TaskDetailSheet.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/tasks/TaskDetailSheet.kt
@@ -1,5 +1,7 @@
package com.avinal.memos.ui.tasks
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
@@ -9,33 +11,25 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FilterChip
-import androidx.compose.material3.HorizontalDivider
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import com.avinal.memos.domain.Task
import com.avinal.memos.parser.TaskParser
-import com.avinal.memos.ui.theme.DuePurple
-import com.avinal.memos.ui.theme.PriorityP1
-import com.avinal.memos.ui.theme.PriorityP2
-import com.avinal.memos.ui.theme.PriorityP3
+import com.avinal.memos.ui.theme.LocalAccentColor
import kotlin.time.Clock
import kotlinx.datetime.DateTimeUnit
-import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus
import kotlinx.datetime.todayIn
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
+@OptIn(ExperimentalLayoutApi::class)
@Composable
fun TaskDetailSheet(
task: Task,
@@ -43,83 +37,88 @@ fun TaskDetailSheet(
onUpdate: (Task, String) -> Unit,
onOpenMemo: (String) -> Unit,
) {
- val sheetState = rememberModalBottomSheetState()
+ val accent = LocalAccentColor.current
+ val textColor = MaterialTheme.colorScheme.onBackground
+ val subtleColor = MaterialTheme.colorScheme.onSurfaceVariant
- ModalBottomSheet(
+ AlertDialog(
onDismissRequest = onDismiss,
- sheetState = sheetState,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
- ) {
- Text(
- text = task.text,
- style = MaterialTheme.typography.titleMedium,
- )
+ containerColor = MaterialTheme.colorScheme.surface,
+ title = null,
+ text = {
+ Column {
+ Text(task.text, fontSize = 17.sp, fontWeight = FontWeight.Medium, color = textColor)
- Spacer(Modifier.height(16.dp))
+ Spacer(Modifier.height(16.dp))
- Text("Due Date", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
- Spacer(Modifier.height(4.dp))
- FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
- val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
- val dateOptions = listOf(
- "Today" to today,
- "Tomorrow" to today.plus(1, DateTimeUnit.DAY),
- "Next week" to today.plus(7, DateTimeUnit.DAY),
- "No date" to null,
+ Text("due date", fontSize = 13.sp, color = subtleColor)
+ Spacer(Modifier.height(6.dp))
+ FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
+ val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
+ listOf(
+ "today" to today,
+ "tomorrow" to today.plus(1, DateTimeUnit.DAY),
+ "next week" to today.plus(7, DateTimeUnit.DAY),
+ "no date" to null,
+ ).forEach { (label, date) ->
+ val isSelected = task.dueDate == date
+ Text(
+ label,
+ fontSize = 14.sp,
+ color = if (isSelected) accent else textColor,
+ fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
+ modifier = Modifier
+ .background(
+ if (isSelected) accent.copy(alpha = 0.1f) else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
+ RoundedCornerShape(4.dp),
+ )
+ .clickable { onUpdate(task, TaskParser.reconstructLine(task.copy(dueDate = date))) }
+ .padding(horizontal = 10.dp, vertical = 6.dp),
+ )
+ }
+ }
+
+ Spacer(Modifier.height(14.dp))
+
+ Text("priority", fontSize = 13.sp, color = subtleColor)
+ Spacer(Modifier.height(6.dp))
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ listOf(null to "none", 1 to "p1", 2 to "p2", 3 to "p3").forEach { (p, label) ->
+ val isSelected = task.priority == p
+ Text(
+ label,
+ fontSize = 14.sp,
+ color = if (isSelected) accent else textColor,
+ fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
+ modifier = Modifier
+ .background(
+ if (isSelected) accent.copy(alpha = 0.1f) else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
+ RoundedCornerShape(4.dp),
+ )
+ .clickable { onUpdate(task, TaskParser.reconstructLine(task.copy(priority = p))) }
+ .padding(horizontal = 10.dp, vertical = 6.dp),
+ )
+ }
+ }
+
+ if (task.labels.isNotEmpty() || task.lists.isNotEmpty()) {
+ Spacer(Modifier.height(12.dp))
+ FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
+ task.labels.forEach { Text("@$it", fontSize = 13.sp, color = subtleColor) }
+ task.lists.forEach { Text("#$it", fontSize = 13.sp, color = accent) }
+ }
+ }
+
+ Spacer(Modifier.height(16.dp))
+
+ Text(
+ "open in memo",
+ fontSize = 15.sp,
+ color = accent,
+ modifier = Modifier.clickable { onOpenMemo(task.memoId) }.padding(vertical = 6.dp),
)
- dateOptions.forEach { (label, date) ->
- FilterChip(
- selected = task.dueDate == date,
- onClick = {
- val updated = task.copy(dueDate = date)
- onUpdate(task, TaskParser.reconstructLine(updated))
- },
- label = { Text(label) },
- )
- }
}
-
- Spacer(Modifier.height(12.dp))
-
- Text("Priority", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
- Spacer(Modifier.height(4.dp))
- Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
- val priorityOptions = listOf(null to "None", 1 to "P1", 2 to "P2", 3 to "P3")
- priorityOptions.forEach { (p, label) ->
- FilterChip(
- selected = task.priority == p,
- onClick = {
- val updated = task.copy(priority = p)
- onUpdate(task, TaskParser.reconstructLine(updated))
- },
- label = { Text(label) },
- )
- }
- }
-
- if (task.labels.isNotEmpty() || task.lists.isNotEmpty()) {
- Spacer(Modifier.height(12.dp))
- FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
- task.labels.forEach { label ->
- Text("@$label", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
- }
- task.lists.forEach { list ->
- Text("#$list", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
- }
- }
- }
-
- Spacer(Modifier.height(16.dp))
- HorizontalDivider()
- Spacer(Modifier.height(8.dp))
-
- TextButton(onClick = { onOpenMemo(task.memoId) }) {
- Text("Open in memo")
- }
-
- Spacer(Modifier.height(16.dp))
- }
- }
+ },
+ confirmButton = {},
+ )
}
diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/theme/Color.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/theme/Color.kt
index 36c5697..a532df7 100644
--- a/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/theme/Color.kt
+++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/ui/theme/Color.kt
@@ -3,7 +3,6 @@ package com.avinal.memos.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d1af71c..1697e26 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,14 +5,14 @@ compose-multiplatform = "1.11.0"
ksp = "2.3.7"
# AndroidX
-core-ktx = "1.16.0"
-activity-compose = "1.10.1"
+core-ktx = "1.18.0"
+activity-compose = "1.13.0"
lifecycle = "2.10.0"
navigation = "2.9.2"
datastore = "1.2.1"
-work = "2.11.1"
+work = "2.11.2"
room = "2.8.4"
-sqlite = "2.5.1"
+sqlite = "2.6.2"
# Kotlin Multiplatform
kotlinx-coroutines = "1.11.0"
@@ -20,15 +20,15 @@ kotlinx-datetime = "0.8.0"
kotlinx-serialization = "1.11.0"
# Networking
-ktor = "3.4.3"
+ktor = "3.5.0"
# Image loading
coil = "3.4.0"
# Testing
junit = "4.13.2"
-junit-android = "1.1.5"
-espresso = "3.5.1"
+junit-android = "1.3.0"
+espresso = "3.7.0"
[libraries]
# AndroidX