@@ -36,6 +36,7 @@ import com.dadao.task.ui.TicketsScreen
|
||||
import com.dadao.task.ui.UpdateDialog
|
||||
import com.dadao.task.ui.WorkspaceScaffold
|
||||
import com.dadao.task.ui.WorkspaceTab
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -114,10 +115,14 @@ private fun AppRoot(joinUri: String?) {
|
||||
.onFailure { pendingJoin = null }
|
||||
}
|
||||
|
||||
// 未读数:进工作区、切页面、或通知页标了已读(unreadRefresh)时刷新
|
||||
// 未读数:进工作区、切页面、或通知页标了已读(unreadRefresh)时刷新;
|
||||
// 停留不动时每 30 秒再拉一次,这样"任务被指派/快到期/已逾期"不用切页面也能看到角标。
|
||||
LaunchedEffect(current?.id, tab, unreadRefresh) {
|
||||
val ws = current ?: return@LaunchedEffect
|
||||
unread = api.unreadCount(ws.id)
|
||||
while (true) {
|
||||
unread = api.unreadCount(ws.id)
|
||||
delay(30_000)
|
||||
}
|
||||
}
|
||||
|
||||
// 版本检查:登录成功后查一次就够,查不到(网络、没发布过包)就当没有新版
|
||||
@@ -234,11 +239,15 @@ private fun AppRoot(joinUri: String?) {
|
||||
WorkspaceTab.Notifications -> NotificationsScreen(
|
||||
api = api,
|
||||
workspaceId = ws.id,
|
||||
onOpenItem = { itemId, isTicket ->
|
||||
// 通知点开后跳到对应列表并展开那一条;通知本身不带数据,页面自己回源
|
||||
onOpenItem = { itemId, itemKind ->
|
||||
// 通知点开后跳到对应页签并展开那一条;通知本身不带数据,页面自己回源
|
||||
reopenItem = itemId
|
||||
reopenKnowledge = false
|
||||
tab = if (isTicket) WorkspaceTab.Tickets else WorkspaceTab.Tasks
|
||||
reopenKnowledge = itemKind == "knowledge"
|
||||
tab = when (itemKind) {
|
||||
"knowledge" -> WorkspaceTab.Knowledge
|
||||
"ticket" -> WorkspaceTab.Tickets
|
||||
else -> WorkspaceTab.Tasks
|
||||
}
|
||||
},
|
||||
onUnreadChanged = { unreadRefresh += 1 },
|
||||
)
|
||||
|
||||
@@ -328,6 +328,8 @@ data class AppNotification(
|
||||
val id: String,
|
||||
val kind: String = "",
|
||||
@SerialName("item_id") val itemId: String? = null,
|
||||
/** task / ticket / knowledge:决定点开跳到哪个页签。 */
|
||||
@SerialName("item_kind") val itemKind: String? = null,
|
||||
@SerialName("item_title") val itemTitle: String? = null,
|
||||
val title: String = "",
|
||||
val body: String = "",
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.dadao.task.data.Api
|
||||
import com.dadao.task.data.ApiException
|
||||
import com.dadao.task.data.AppNotification
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -44,7 +45,7 @@ import kotlinx.coroutines.launch
|
||||
fun NotificationsScreen(
|
||||
api: Api,
|
||||
workspaceId: String,
|
||||
onOpenItem: (itemId: String, isTicket: Boolean) -> Unit,
|
||||
onOpenItem: (itemId: String, itemKind: String) -> Unit,
|
||||
onUnreadChanged: () -> Unit,
|
||||
refreshKey: Int = 0,
|
||||
) {
|
||||
@@ -54,17 +55,25 @@ fun NotificationsScreen(
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
suspend fun reload() {
|
||||
loading = true
|
||||
suspend fun reload(silent: Boolean = false) {
|
||||
if (!silent) loading = true
|
||||
error = null
|
||||
runCatching { api.notifications(workspaceId, unreadOnly) }
|
||||
.onSuccess { rows = it.notifications }
|
||||
.onFailure { error = verifyStatusCode((it as? ApiException)?.code ?: 0) }
|
||||
loading = false
|
||||
if (!silent) loading = false
|
||||
}
|
||||
|
||||
LaunchedEffect(workspaceId, unreadOnly, refreshKey) { reload() }
|
||||
|
||||
// 停在通知页时也要能看到新动静:每 30 秒静默拉一次,不闪加载态。
|
||||
LaunchedEffect(workspaceId, unreadOnly) {
|
||||
while (true) {
|
||||
delay(30_000)
|
||||
reload(silent = true)
|
||||
}
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxSize().padding(horizontal = 16.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -85,6 +94,26 @@ fun NotificationsScreen(
|
||||
bg = if (unreadOnly) PaperSunken else PrimarySoft,
|
||||
modifier = Modifier.clickable { unreadOnly = false },
|
||||
)
|
||||
Box(Modifier.weight(1f))
|
||||
SoftPill(
|
||||
text = "刷新",
|
||||
fg = InkMuted,
|
||||
bg = PaperSunken,
|
||||
modifier = Modifier.clickable { scope.launch { reload() } },
|
||||
)
|
||||
SoftPill(
|
||||
text = "全部已读",
|
||||
fg = InkMuted,
|
||||
bg = PaperSunken,
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
runCatching { api.markAllNotificationsRead(workspaceId) }
|
||||
rows = rows.map { if (it.readAt == null) it.copy(readAt = "read") else it }
|
||||
onUnreadChanged()
|
||||
reload(silent = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
@@ -121,7 +150,14 @@ fun NotificationsScreen(
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 20.dp),
|
||||
) {
|
||||
items(rows, key = { it.id }) { n ->
|
||||
val isTicket = n.kind.startsWith("ticket_")
|
||||
// 知识/工单/任务三种落点:优先看后端给的 item_kind,老数据再退回 kind 前缀
|
||||
val isKnowledge = n.kind.startsWith("knowledge_") || n.itemKind == "knowledge"
|
||||
val isTicket = !isKnowledge && (n.kind.startsWith("ticket_") || n.itemKind == "ticket")
|
||||
val targetKind = when {
|
||||
isKnowledge -> "knowledge"
|
||||
isTicket -> "ticket"
|
||||
else -> "task"
|
||||
}
|
||||
SoftCard(
|
||||
Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
@@ -131,7 +167,7 @@ fun NotificationsScreen(
|
||||
rows = rows.map { if (it.id == n.id) it.copy(readAt = "read") else it }
|
||||
onUnreadChanged()
|
||||
}
|
||||
n.itemId?.let { onOpenItem(it, isTicket) }
|
||||
n.itemId?.let { onOpenItem(it, targetKind) }
|
||||
}
|
||||
},
|
||||
) {
|
||||
@@ -143,7 +179,11 @@ fun NotificationsScreen(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = if (isTicket) "工" else "记",
|
||||
text = when {
|
||||
isKnowledge -> "知"
|
||||
isTicket -> "工"
|
||||
else -> "记"
|
||||
},
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (n.readAt == null) Primary else InkMuted,
|
||||
|
||||
Reference in New Issue
Block a user