feat(android): 设计系统与五个页面重做,并补齐升级工单、邀请、看板
此前安卓只到"能登录、能看列表",且从没编译过。这轮把它做到与 Web 同构:
- Theme.kt 与 Tailwind 同一套色板与字号阶;新增 Widgets.kt(状态 pill、
卡片、按钮、头像、空态、时间与 action 的中文映射、错误码文案)
- 新增 AppFrame.kt(顶栏工作区 + 未读角标 + 底部四 tab)与
TaskDetailSheet / BoardScreen / MembersScreen
- 重写任务、工单、知识三页与登录、首页;任务详情里补上「升级成工单」
和「沉淀成知识」——这两个入口此前只有 Web 有,是真正的功能欠账
- 看板不做拖拽:一列已占 76% 屏宽,拖拽命中率太差,改为点卡片选目标列
- 修运行期 bug:workspaces/members 按 List 解析而服务端返回 {items:[...]};
消息区被手势条遮挡;升级工单候选人里出现自己;priority=0 显示"无"
- android/scripts/emulator.ps1 固化构建+装机+起模拟器;assembleDebug 出 APK
已在 API 35 模拟器上逐页跑通并截图(见 2.Design/screens/android-*.png)。
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package com.dadao.task
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -13,15 +15,20 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.dadao.task.data.Api
|
||||
import com.dadao.task.data.TokenStore
|
||||
import com.dadao.task.data.Workspace
|
||||
import com.dadao.task.ui.BoardScreen
|
||||
import com.dadao.task.ui.DadaoTheme
|
||||
import com.dadao.task.ui.HomeScreen
|
||||
import com.dadao.task.ui.KnowledgeScreen
|
||||
import com.dadao.task.ui.LoginScreen
|
||||
import com.dadao.task.ui.MembersScreen
|
||||
import com.dadao.task.ui.TaskListScreen
|
||||
import com.dadao.task.ui.TicketsScreen
|
||||
import com.dadao.task.ui.WorkspaceScaffold
|
||||
import com.dadao.task.ui.WorkspaceTab
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -30,22 +37,37 @@ class MainActivity : ComponentActivity() {
|
||||
setContent {
|
||||
DadaoTheme {
|
||||
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
AppRoot()
|
||||
AppRoot(joinUri = intent?.dataString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Screen { Splash, Login, Home, Tasks, Tickets, Knowledge }
|
||||
private enum class Screen { Splash, Login, Home, Workspace }
|
||||
|
||||
/** 从 https://dadaotask.com/join/<workspaceId>/<code> 里拆出工作区与邀请码。 */
|
||||
private fun parseJoin(uri: String?): Pair<String, String>? {
|
||||
if (uri.isNullOrBlank()) return null
|
||||
val parts = uri.trimEnd('/').split("/")
|
||||
val idx = parts.indexOf("join")
|
||||
if (idx < 0 || parts.size < idx + 3) return null
|
||||
val ws = parts[idx + 1]
|
||||
val code = parts[idx + 2]
|
||||
if (ws.length < 10 || code.length < 4) return null
|
||||
return ws to code
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppRoot() {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
private fun AppRoot(joinUri: String?) {
|
||||
val context = LocalContext.current
|
||||
val tokens = remember { TokenStore(context) }
|
||||
val api = remember { Api(tokens) }
|
||||
var screen by remember { mutableStateOf(if (tokens.isLoggedIn) Screen.Splash else Screen.Login) }
|
||||
var pendingJoin by remember { mutableStateOf(parseJoin(joinUri)) }
|
||||
var current by remember { mutableStateOf<Workspace?>(null) }
|
||||
var tab by remember { mutableStateOf(WorkspaceTab.Tasks) }
|
||||
var unread by remember { mutableStateOf(0) }
|
||||
|
||||
// 有本地令牌就先静默校验一次:令牌过期/被吊销要退回登录页,而不是进了主页再崩。
|
||||
LaunchedEffect(screen) {
|
||||
@@ -60,8 +82,28 @@ private fun AppRoot() {
|
||||
}
|
||||
}
|
||||
|
||||
// 邀请链接:登录之后自动加入,加入完直接进那个工作区。
|
||||
LaunchedEffect(screen, pendingJoin) {
|
||||
val join = pendingJoin ?: return@LaunchedEffect
|
||||
if (screen != Screen.Home) return@LaunchedEffect
|
||||
runCatching { api.join(join.first, join.second) }
|
||||
.onSuccess {
|
||||
current = it
|
||||
tab = WorkspaceTab.Tasks
|
||||
pendingJoin = null
|
||||
screen = Screen.Workspace
|
||||
}
|
||||
.onFailure { pendingJoin = null }
|
||||
}
|
||||
|
||||
// 工单未读数:进工作区就拉一次,切页面时再刷。
|
||||
LaunchedEffect(current?.id, tab) {
|
||||
val ws = current ?: return@LaunchedEffect
|
||||
unread = api.unreadCount(ws.id)
|
||||
}
|
||||
|
||||
when (screen) {
|
||||
Screen.Splash -> Unit // 校验中,空白过渡即可
|
||||
Screen.Splash -> Unit
|
||||
Screen.Login -> LoginScreen(api = api, onLoggedIn = { screen = Screen.Home })
|
||||
Screen.Home -> HomeScreen(
|
||||
api = api,
|
||||
@@ -71,37 +113,29 @@ private fun AppRoot() {
|
||||
},
|
||||
onOpenWorkspace = { ws ->
|
||||
current = ws
|
||||
screen = Screen.Tasks
|
||||
tab = WorkspaceTab.Tasks
|
||||
screen = Screen.Workspace
|
||||
},
|
||||
)
|
||||
Screen.Tasks -> {
|
||||
Screen.Workspace -> {
|
||||
val ws = current
|
||||
if (ws == null) {
|
||||
screen = Screen.Home
|
||||
} else {
|
||||
TaskListScreen(
|
||||
api = api,
|
||||
WorkspaceScaffold(
|
||||
workspace = ws,
|
||||
onBack = { screen = Screen.Home },
|
||||
onOpenTickets = { screen = Screen.Tickets },
|
||||
onOpenKnowledge = { screen = Screen.Knowledge },
|
||||
)
|
||||
}
|
||||
}
|
||||
Screen.Tickets -> {
|
||||
val ws = current
|
||||
if (ws == null) {
|
||||
screen = Screen.Home
|
||||
} else {
|
||||
TicketsScreen(api = api, workspace = ws, onBack = { screen = Screen.Tasks })
|
||||
}
|
||||
}
|
||||
Screen.Knowledge -> {
|
||||
val ws = current
|
||||
if (ws == null) {
|
||||
screen = Screen.Home
|
||||
} else {
|
||||
KnowledgeScreen(api = api, workspace = ws, onBack = { screen = Screen.Tasks })
|
||||
tab = tab,
|
||||
unread = unread,
|
||||
onTab = { tab = it },
|
||||
onBackHome = { screen = Screen.Home },
|
||||
) {
|
||||
when (tab) {
|
||||
WorkspaceTab.Tasks -> TaskListScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Knowledge -> KnowledgeScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user