openuispec 0.1.25 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -18
- package/cli/index.ts +21 -3
- package/cli/init.ts +27 -11
- package/docs/implementation-notes.md +119 -0
- package/docs/release-notes-v0.1.26.md +64 -0
- package/docs/release-notes-v0.1.27.md +28 -0
- package/docs/release-notes-v0.1.28.md +25 -0
- package/docs/stress-test-maturity-report.md +1 -1
- package/drift/index.ts +396 -22
- package/examples/taskflow/AGENTS.md +112 -0
- package/examples/taskflow/CLAUDE.md +112 -0
- package/examples/taskflow/generated/android/TaskFlow/README.md +43 -0
- package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +76 -0
- package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +1 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +21 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +19 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +283 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +106 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +57 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +109 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +112 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +61 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +82 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +111 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +77 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +30 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +86 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +57 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +155 -0
- package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +4 -0
- package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +5 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +12 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/examples/taskflow/generated/android/TaskFlow/gradle.properties +4 -0
- package/examples/taskflow/generated/android/TaskFlow/gradlew +18 -0
- package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +12 -0
- package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +18 -0
- package/examples/taskflow/generated/ios/TaskFlow/README.md +21 -0
- package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +115 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +24 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +150 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +220 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +122 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +21 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +201 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +48 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +59 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +63 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +85 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +219 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +320 -0
- package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +41 -0
- package/examples/taskflow/generated/ios/TaskFlow/project.yml +26 -0
- package/examples/taskflow/generated/web/TaskFlow/README.md +19 -0
- package/examples/taskflow/generated/web/TaskFlow/index.html +12 -0
- package/examples/taskflow/generated/web/TaskFlow/package-lock.json +1908 -0
- package/examples/taskflow/generated/web/TaskFlow/package.json +24 -0
- package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +58 -0
- package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +55 -0
- package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +82 -0
- package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +191 -0
- package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +41 -0
- package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +131 -0
- package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +25 -0
- package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +39 -0
- package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +111 -0
- package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +13 -0
- package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +111 -0
- package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +82 -0
- package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +132 -0
- package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +105 -0
- package/examples/taskflow/generated/web/TaskFlow/src/store.ts +216 -0
- package/examples/taskflow/generated/web/TaskFlow/src/styles.css +617 -0
- package/examples/taskflow/generated/web/TaskFlow/src/types.ts +64 -0
- package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +78 -0
- package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +21 -0
- package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +6 -0
- package/examples/taskflow/openuispec/README.md +49 -0
- package/examples/todo-orbit/AGENTS.md +46 -14
- package/examples/todo-orbit/CLAUDE.md +46 -14
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +69 -18
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +5 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +5 -2
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +1 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +3 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +2 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +1 -1
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +59 -6
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +40 -0
- package/examples/todo-orbit/openuispec/README.md +24 -131
- package/examples/todo-orbit/openuispec/flows/create_recurring_rule.yaml +3 -0
- package/examples/todo-orbit/openuispec/flows/create_task.yaml +1 -0
- package/examples/todo-orbit/openuispec/flows/edit_task.yaml +1 -0
- package/examples/todo-orbit/openuispec/locales/en.json +1 -0
- package/examples/todo-orbit/openuispec/locales/ru.json +1 -0
- package/examples/todo-orbit/openuispec/screens/task_detail.yaml +1 -0
- package/examples/todo-orbit/openuispec/tokens/icons.yaml +6 -0
- package/package.json +6 -1
- package/prepare/index.ts +391 -0
- package/schema/semantic-lint.ts +592 -0
- package/schema/validate.ts +17 -13
- package/status/index.ts +200 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/README.md +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/action_trigger.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/collection.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/data_display.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/feedback.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/input_field.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/nav_container.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/surface.yaml +0 -0
- /package/examples/taskflow/{contracts → openuispec/contracts}/x_media_player.yaml +0 -0
- /package/examples/taskflow/{flows → openuispec/flows}/create_task.yaml +0 -0
- /package/examples/taskflow/{flows → openuispec/flows}/edit_task.yaml +0 -0
- /package/examples/taskflow/{locales → openuispec/locales}/en.json +0 -0
- /package/examples/taskflow/{openuispec.yaml → openuispec/openuispec.yaml} +0 -0
- /package/examples/taskflow/{platform → openuispec/platform}/android.yaml +0 -0
- /package/examples/taskflow/{platform → openuispec/platform}/ios.yaml +0 -0
- /package/examples/taskflow/{platform → openuispec/platform}/web.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/calendar.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/home.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/profile_edit.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/project_detail.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/projects.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/settings.yaml +0 -0
- /package/examples/taskflow/{screens → openuispec/screens}/task_detail.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/color.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/elevation.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/icons.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/layout.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/motion.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/spacing.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/themes.yaml +0 -0
- /package/examples/taskflow/{tokens → openuispec/tokens}/typography.yaml +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.model
|
|
2
|
+
|
|
3
|
+
import androidx.compose.material.icons.Icons
|
|
4
|
+
import androidx.compose.material.icons.outlined.CheckCircle
|
|
5
|
+
import androidx.compose.material.icons.outlined.Folder
|
|
6
|
+
import androidx.compose.material.icons.outlined.Star
|
|
7
|
+
import androidx.compose.ui.graphics.Color
|
|
8
|
+
import java.time.LocalDate
|
|
9
|
+
import java.time.LocalDateTime
|
|
10
|
+
|
|
11
|
+
object SampleData {
|
|
12
|
+
val users = listOf(
|
|
13
|
+
User("u1", "Amina Patel", "Amina", "amina@taskflow.app"),
|
|
14
|
+
User("u2", "Jonas Reed", "Jonas", "jonas@taskflow.app"),
|
|
15
|
+
User("u3", "Mika Chen", "Mika", "mika@taskflow.app")
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
val projects = listOf(
|
|
19
|
+
Project("p1", "Product Launch", Color(0xFF5B4FE8), Icons.Outlined.Star, 3),
|
|
20
|
+
Project("p2", "Website Refresh", Color(0xFF3B82D4), Icons.Outlined.Folder, 2),
|
|
21
|
+
Project("p3", "Sprint Rituals", Color(0xFF2D9D5E), Icons.Outlined.CheckCircle, 2)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
fun tasks(currentUser: User): List<Task> = listOf(
|
|
25
|
+
Task(
|
|
26
|
+
id = "t1",
|
|
27
|
+
title = "Finalize launch checklist",
|
|
28
|
+
description = "Confirm release notes, QA sign-off, and staged rollout timing.",
|
|
29
|
+
status = TaskStatus.InProgress,
|
|
30
|
+
priority = Priority.High,
|
|
31
|
+
dueDate = LocalDate.now(),
|
|
32
|
+
projectId = "p1",
|
|
33
|
+
tags = listOf("launch", "ops"),
|
|
34
|
+
createdAt = LocalDateTime.now().minusDays(4),
|
|
35
|
+
updatedAt = LocalDateTime.now().minusHours(2),
|
|
36
|
+
assignee = currentUser,
|
|
37
|
+
attachment = MediaAttachment(
|
|
38
|
+
source = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
|
39
|
+
mediaType = "video",
|
|
40
|
+
title = "Launch teaser draft"
|
|
41
|
+
)
|
|
42
|
+
),
|
|
43
|
+
Task(
|
|
44
|
+
id = "t2",
|
|
45
|
+
title = "Draft homepage copy",
|
|
46
|
+
description = "Need a stronger value proposition and one proof block for social trust.",
|
|
47
|
+
status = TaskStatus.Todo,
|
|
48
|
+
priority = Priority.Medium,
|
|
49
|
+
dueDate = LocalDate.now().plusDays(2),
|
|
50
|
+
projectId = "p2",
|
|
51
|
+
tags = listOf("copy", "marketing"),
|
|
52
|
+
createdAt = LocalDateTime.now().minusDays(2),
|
|
53
|
+
updatedAt = LocalDateTime.now().minusHours(10),
|
|
54
|
+
assignee = users[1]
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.Row
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
9
|
+
import androidx.compose.foundation.layout.padding
|
|
10
|
+
import androidx.compose.foundation.layout.size
|
|
11
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
12
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
13
|
+
import androidx.compose.material.icons.Icons
|
|
14
|
+
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
|
|
15
|
+
import androidx.compose.material.icons.outlined.CheckCircle
|
|
16
|
+
import androidx.compose.material3.Card
|
|
17
|
+
import androidx.compose.material3.ElevatedCard
|
|
18
|
+
import androidx.compose.material3.Icon
|
|
19
|
+
import androidx.compose.material3.MaterialTheme
|
|
20
|
+
import androidx.compose.material3.Text
|
|
21
|
+
import androidx.compose.material3.TextButton
|
|
22
|
+
import androidx.compose.runtime.Composable
|
|
23
|
+
import androidx.compose.ui.Alignment
|
|
24
|
+
import androidx.compose.ui.Modifier
|
|
25
|
+
import androidx.compose.ui.graphics.Color
|
|
26
|
+
import androidx.compose.ui.graphics.vector.ImageVector
|
|
27
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
28
|
+
import androidx.compose.ui.unit.dp
|
|
29
|
+
|
|
30
|
+
@Composable
|
|
31
|
+
fun EmptyStateCard(title: String, body: String) {
|
|
32
|
+
ElevatedCard {
|
|
33
|
+
Column(
|
|
34
|
+
modifier = Modifier
|
|
35
|
+
.fillMaxWidth()
|
|
36
|
+
.padding(24.dp),
|
|
37
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
38
|
+
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
39
|
+
) {
|
|
40
|
+
Icon(Icons.Outlined.CheckCircle, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(36.dp))
|
|
41
|
+
Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
|
42
|
+
Text(body, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Composable
|
|
48
|
+
fun SectionCard(title: String, content: @Composable () -> Unit) {
|
|
49
|
+
ElevatedCard {
|
|
50
|
+
Column(
|
|
51
|
+
modifier = Modifier
|
|
52
|
+
.fillMaxWidth()
|
|
53
|
+
.padding(16.dp),
|
|
54
|
+
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
55
|
+
) {
|
|
56
|
+
Text(title, style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
57
|
+
content()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Composable
|
|
63
|
+
fun StatCard(label: String, value: String) {
|
|
64
|
+
Card {
|
|
65
|
+
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
66
|
+
Text(label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
67
|
+
Text(value, fontWeight = FontWeight.SemiBold)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Composable
|
|
73
|
+
fun DetailRow(icon: ImageVector, label: String, value: String, onClick: (() -> Unit)? = null) {
|
|
74
|
+
val content: @Composable () -> Unit = {
|
|
75
|
+
Row(
|
|
76
|
+
modifier = Modifier
|
|
77
|
+
.fillMaxWidth()
|
|
78
|
+
.padding(vertical = 8.dp),
|
|
79
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
80
|
+
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
81
|
+
) {
|
|
82
|
+
Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
83
|
+
Column(modifier = Modifier.weight(1f)) {
|
|
84
|
+
Text(label, style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
85
|
+
Text(value, fontWeight = FontWeight.Medium)
|
|
86
|
+
}
|
|
87
|
+
if (onClick != null) Icon(Icons.AutoMirrored.Outlined.KeyboardArrowRight, contentDescription = null)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (onClick != null) TextButton(onClick = onClick, modifier = Modifier.fillMaxWidth()) { content() } else content()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Composable
|
|
94
|
+
fun Avatar(name: String) {
|
|
95
|
+
val initials = name.split(" ").take(2).joinToString("") { it.firstOrNull()?.uppercase() ?: "" }
|
|
96
|
+
Box(
|
|
97
|
+
modifier = Modifier
|
|
98
|
+
.size(40.dp)
|
|
99
|
+
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.14f), CircleShape),
|
|
100
|
+
contentAlignment = Alignment.Center
|
|
101
|
+
) {
|
|
102
|
+
Text(initials, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Composable
|
|
107
|
+
fun PriorityDot(color: Color) {
|
|
108
|
+
Box(modifier = Modifier.size(12.dp).background(color, RoundedCornerShape(999.dp)))
|
|
109
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.screens
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.PaddingValues
|
|
6
|
+
import androidx.compose.foundation.layout.Row
|
|
7
|
+
import androidx.compose.foundation.layout.Spacer
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
9
|
+
import androidx.compose.foundation.layout.height
|
|
10
|
+
import androidx.compose.foundation.layout.width
|
|
11
|
+
import androidx.compose.foundation.lazy.LazyColumn
|
|
12
|
+
import androidx.compose.foundation.lazy.items
|
|
13
|
+
import androidx.compose.material.icons.Icons
|
|
14
|
+
import androidx.compose.material.icons.outlined.Add
|
|
15
|
+
import androidx.compose.material.icons.outlined.Search
|
|
16
|
+
import androidx.compose.material3.Button
|
|
17
|
+
import androidx.compose.material3.ElevatedCard
|
|
18
|
+
import androidx.compose.material3.FilterChip
|
|
19
|
+
import androidx.compose.material3.Icon
|
|
20
|
+
import androidx.compose.material3.MaterialTheme
|
|
21
|
+
import androidx.compose.material3.OutlinedTextField
|
|
22
|
+
import androidx.compose.material3.Text
|
|
23
|
+
import androidx.compose.runtime.Composable
|
|
24
|
+
import androidx.compose.ui.Alignment
|
|
25
|
+
import androidx.compose.ui.Modifier
|
|
26
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
27
|
+
import androidx.compose.ui.unit.dp
|
|
28
|
+
import uz.rsteam.taskflow.model.Project
|
|
29
|
+
import uz.rsteam.taskflow.model.Task
|
|
30
|
+
import uz.rsteam.taskflow.model.TaskFilter
|
|
31
|
+
import uz.rsteam.taskflow.model.TaskStatus
|
|
32
|
+
import uz.rsteam.taskflow.model.User
|
|
33
|
+
import uz.rsteam.taskflow.ui.components.EmptyStateCard
|
|
34
|
+
import uz.rsteam.taskflow.ui.components.PriorityDot
|
|
35
|
+
import java.time.LocalDate
|
|
36
|
+
|
|
37
|
+
@Composable
|
|
38
|
+
fun HomeScreen(
|
|
39
|
+
currentUser: User,
|
|
40
|
+
projects: List<Project>,
|
|
41
|
+
tasks: List<Task>,
|
|
42
|
+
activeFilter: TaskFilter,
|
|
43
|
+
searchQuery: String,
|
|
44
|
+
onSearchChange: (String) -> Unit,
|
|
45
|
+
onFilterChange: (TaskFilter) -> Unit,
|
|
46
|
+
onSelectTask: (String) -> Unit,
|
|
47
|
+
onCreateTask: () -> Unit
|
|
48
|
+
) {
|
|
49
|
+
val filteredTasks = tasks.filter { task ->
|
|
50
|
+
val filterMatch = when (activeFilter) {
|
|
51
|
+
TaskFilter.All -> true
|
|
52
|
+
TaskFilter.Today -> task.dueDate == LocalDate.now()
|
|
53
|
+
TaskFilter.Upcoming -> task.dueDate?.isAfter(LocalDate.now()) == true
|
|
54
|
+
TaskFilter.Done -> task.status == TaskStatus.Done
|
|
55
|
+
}
|
|
56
|
+
val haystack = (task.title + " " + (task.description ?: "") + " " + task.tags.joinToString(" ") + " " + (projects.firstOrNull { it.id == task.projectId }?.name ?: ""))
|
|
57
|
+
filterMatch && haystack.contains(searchQuery, ignoreCase = true)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
LazyColumn(contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
61
|
+
item {
|
|
62
|
+
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
63
|
+
Text("Good day, ${currentUser.firstName}", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
64
|
+
Text("${filteredTasks.size} tasks visible", color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
item {
|
|
68
|
+
OutlinedTextField(
|
|
69
|
+
value = searchQuery,
|
|
70
|
+
onValueChange = onSearchChange,
|
|
71
|
+
modifier = Modifier.fillMaxWidth(),
|
|
72
|
+
leadingIcon = { Icon(Icons.Outlined.Search, contentDescription = null) },
|
|
73
|
+
placeholder = { Text("Search tasks") }
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
item {
|
|
77
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
78
|
+
TaskFilter.entries.forEach { filter ->
|
|
79
|
+
FilterChip(selected = filter == activeFilter, onClick = { onFilterChange(filter) }, label = { Text(filter.name) })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
item {
|
|
84
|
+
Button(onClick = onCreateTask) {
|
|
85
|
+
Icon(Icons.Outlined.Add, contentDescription = null)
|
|
86
|
+
Spacer(Modifier.width(8.dp))
|
|
87
|
+
Text("New task")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (filteredTasks.isEmpty()) {
|
|
92
|
+
item { EmptyStateCard(title = "All caught up!", body = "No tasks match this filter") }
|
|
93
|
+
} else {
|
|
94
|
+
items(filteredTasks, key = { it.id }) { task ->
|
|
95
|
+
ElevatedCard(onClick = { onSelectTask(task.id) }) {
|
|
96
|
+
Row(
|
|
97
|
+
modifier = Modifier.fillMaxWidth(),
|
|
98
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
99
|
+
verticalAlignment = Alignment.CenterVertically
|
|
100
|
+
) {
|
|
101
|
+
Column(modifier = Modifier.weight(1f)) {
|
|
102
|
+
Text(task.title, fontWeight = FontWeight.SemiBold)
|
|
103
|
+
Text(task.tags.joinToString(", "), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
|
104
|
+
}
|
|
105
|
+
PriorityDot(task.priority.color)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
item { Spacer(Modifier.height(80.dp)) }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.screens
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.lazy.grid.GridCells
|
|
9
|
+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
10
|
+
import androidx.compose.foundation.lazy.grid.items
|
|
11
|
+
import androidx.compose.material3.Button
|
|
12
|
+
import androidx.compose.material3.ElevatedCard
|
|
13
|
+
import androidx.compose.material3.MaterialTheme
|
|
14
|
+
import androidx.compose.material3.Text
|
|
15
|
+
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.ui.Modifier
|
|
17
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import uz.rsteam.taskflow.model.Project
|
|
20
|
+
import uz.rsteam.taskflow.model.Task
|
|
21
|
+
import uz.rsteam.taskflow.ui.components.EmptyStateCard
|
|
22
|
+
|
|
23
|
+
@Composable
|
|
24
|
+
fun ProjectsScreen(projects: List<Project>, onOpenProject: (String) -> Unit, onNewProject: () -> Unit) {
|
|
25
|
+
Column(modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
26
|
+
Text("Projects", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
27
|
+
Button(onClick = onNewProject) { Text("New project") }
|
|
28
|
+
if (projects.isEmpty()) {
|
|
29
|
+
EmptyStateCard("No projects yet", "Create a project to organize your tasks")
|
|
30
|
+
} else {
|
|
31
|
+
LazyVerticalGrid(columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
32
|
+
items(projects, key = { it.id }) { project ->
|
|
33
|
+
ElevatedCard(onClick = { onOpenProject(project.id) }) {
|
|
34
|
+
Column(modifier = Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
35
|
+
Text(project.name, fontWeight = FontWeight.SemiBold)
|
|
36
|
+
Text("${project.taskCount} tasks")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Composable
|
|
46
|
+
fun ProjectDetailScreen(project: Project, tasks: List<Task>, onOpenTask: (String) -> Unit) {
|
|
47
|
+
Column(modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
48
|
+
Text(project.name, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
49
|
+
if (tasks.isEmpty()) {
|
|
50
|
+
EmptyStateCard("No tasks in this project", "Add a task to get started")
|
|
51
|
+
} else {
|
|
52
|
+
tasks.forEach { task ->
|
|
53
|
+
ElevatedCard(onClick = { onOpenTask(task.id) }) {
|
|
54
|
+
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
|
|
55
|
+
Text(task.title, fontWeight = FontWeight.SemiBold)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.screens
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.rememberScrollState
|
|
9
|
+
import androidx.compose.foundation.verticalScroll
|
|
10
|
+
import androidx.compose.material3.Button
|
|
11
|
+
import androidx.compose.material3.ElevatedCard
|
|
12
|
+
import androidx.compose.material3.MaterialTheme
|
|
13
|
+
import androidx.compose.material3.OutlinedTextField
|
|
14
|
+
import androidx.compose.material3.Switch
|
|
15
|
+
import androidx.compose.material3.Text
|
|
16
|
+
import androidx.compose.runtime.Composable
|
|
17
|
+
import androidx.compose.runtime.mutableStateOf
|
|
18
|
+
import androidx.compose.runtime.remember
|
|
19
|
+
import androidx.compose.ui.Modifier
|
|
20
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
21
|
+
import androidx.compose.ui.unit.dp
|
|
22
|
+
import uz.rsteam.taskflow.model.Preferences
|
|
23
|
+
import uz.rsteam.taskflow.model.User
|
|
24
|
+
import uz.rsteam.taskflow.ui.components.Avatar
|
|
25
|
+
|
|
26
|
+
@Composable
|
|
27
|
+
fun SettingsScreen(
|
|
28
|
+
currentUser: User,
|
|
29
|
+
preferences: Preferences,
|
|
30
|
+
onPreferencesChange: (Preferences) -> Unit,
|
|
31
|
+
onEditProfile: () -> Unit
|
|
32
|
+
) {
|
|
33
|
+
Column(
|
|
34
|
+
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(24.dp),
|
|
35
|
+
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
36
|
+
) {
|
|
37
|
+
ElevatedCard(onClick = onEditProfile) {
|
|
38
|
+
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
|
|
39
|
+
Avatar(currentUser.name)
|
|
40
|
+
Text(currentUser.name, fontWeight = FontWeight.SemiBold)
|
|
41
|
+
Text(currentUser.email)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Text("Preferences", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
|
46
|
+
|
|
47
|
+
ElevatedCard {
|
|
48
|
+
Column(modifier = Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
49
|
+
Text("Default priority: ${preferences.defaultPriority.name}")
|
|
50
|
+
Text("Notifications")
|
|
51
|
+
Switch(checked = preferences.notificationsEnabled, onCheckedChange = {
|
|
52
|
+
onPreferencesChange(preferences.copy(notificationsEnabled = it))
|
|
53
|
+
})
|
|
54
|
+
Text("Reminders")
|
|
55
|
+
Switch(checked = preferences.remindersEnabled, onCheckedChange = {
|
|
56
|
+
onPreferencesChange(preferences.copy(remindersEnabled = it))
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Composable
|
|
64
|
+
fun ProfileEditScreen(currentUser: User, onSave: (String, String) -> Unit) {
|
|
65
|
+
val name = remember { mutableStateOf(currentUser.name) }
|
|
66
|
+
val email = remember { mutableStateOf(currentUser.email) }
|
|
67
|
+
|
|
68
|
+
Column(modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
69
|
+
Text("Edit profile", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
70
|
+
OutlinedTextField(value = name.value, onValueChange = { name.value = it }, label = { Text("Name") })
|
|
71
|
+
OutlinedTextField(value = email.value, onValueChange = { email.value = it }, label = { Text("Email") })
|
|
72
|
+
Button(onClick = { onSave(name.value, email.value) }) { Text("Save") }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Composable
|
|
77
|
+
fun CalendarScreen() {
|
|
78
|
+
Column(modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.Center) {
|
|
79
|
+
Text("Calendar", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
80
|
+
Text("Coming in a future version")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.screens
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import android.widget.MediaController
|
|
5
|
+
import android.widget.VideoView
|
|
6
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
7
|
+
import androidx.compose.foundation.layout.Column
|
|
8
|
+
import androidx.compose.foundation.layout.Spacer
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.height
|
|
12
|
+
import androidx.compose.foundation.layout.padding
|
|
13
|
+
import androidx.compose.foundation.layout.width
|
|
14
|
+
import androidx.compose.foundation.rememberScrollState
|
|
15
|
+
import androidx.compose.foundation.verticalScroll
|
|
16
|
+
import androidx.compose.material.icons.Icons
|
|
17
|
+
import androidx.compose.material.icons.automirrored.outlined.Label
|
|
18
|
+
import androidx.compose.material.icons.outlined.Delete
|
|
19
|
+
import androidx.compose.material.icons.outlined.Edit
|
|
20
|
+
import androidx.compose.material.icons.outlined.Folder
|
|
21
|
+
import androidx.compose.material.icons.outlined.Person
|
|
22
|
+
import androidx.compose.material.icons.outlined.Schedule
|
|
23
|
+
import androidx.compose.material3.Button
|
|
24
|
+
import androidx.compose.material3.Icon
|
|
25
|
+
import androidx.compose.material3.MaterialTheme
|
|
26
|
+
import androidx.compose.material3.OutlinedButton
|
|
27
|
+
import androidx.compose.material3.Text
|
|
28
|
+
import androidx.compose.runtime.Composable
|
|
29
|
+
import androidx.compose.ui.Modifier
|
|
30
|
+
import androidx.compose.ui.platform.LocalContext
|
|
31
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
32
|
+
import androidx.compose.ui.unit.dp
|
|
33
|
+
import androidx.compose.ui.viewinterop.AndroidView
|
|
34
|
+
import uz.rsteam.taskflow.model.Project
|
|
35
|
+
import uz.rsteam.taskflow.model.Task
|
|
36
|
+
import uz.rsteam.taskflow.ui.components.DetailRow
|
|
37
|
+
import uz.rsteam.taskflow.ui.components.SectionCard
|
|
38
|
+
import uz.rsteam.taskflow.ui.components.StatCard
|
|
39
|
+
import java.time.format.DateTimeFormatter
|
|
40
|
+
import java.time.format.FormatStyle
|
|
41
|
+
|
|
42
|
+
@Composable
|
|
43
|
+
fun TaskDetailScreen(
|
|
44
|
+
task: Task,
|
|
45
|
+
project: Project?,
|
|
46
|
+
onEdit: () -> Unit,
|
|
47
|
+
onToggleStatus: () -> Unit,
|
|
48
|
+
onDelete: () -> Unit,
|
|
49
|
+
onProjectClick: () -> Unit,
|
|
50
|
+
onAssign: () -> Unit
|
|
51
|
+
) {
|
|
52
|
+
Column(
|
|
53
|
+
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(24.dp),
|
|
54
|
+
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
55
|
+
) {
|
|
56
|
+
Text(task.title, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
57
|
+
|
|
58
|
+
SectionCard("Overview") {
|
|
59
|
+
StatCard("Status", task.status.name)
|
|
60
|
+
Spacer(Modifier.height(8.dp))
|
|
61
|
+
StatCard("Priority", task.priority.name)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
task.description?.let {
|
|
65
|
+
SectionCard("Description") { Text(it) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
task.attachment?.let {
|
|
69
|
+
SectionCard("Media") {
|
|
70
|
+
Text(it.title, fontWeight = FontWeight.SemiBold)
|
|
71
|
+
if (it.mediaType == "video") InlineVideoPlayer(it.source)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
SectionCard("Details") {
|
|
76
|
+
DetailRow(Icons.Outlined.Folder, "Project", project?.name ?: "Unknown", onProjectClick)
|
|
77
|
+
DetailRow(Icons.Outlined.Person, "Assignee", task.assignee?.name ?: "Unassigned", onAssign)
|
|
78
|
+
DetailRow(Icons.AutoMirrored.Outlined.Label, "Tags", task.tags.joinToString(", ").ifBlank { "—" })
|
|
79
|
+
DetailRow(Icons.Outlined.Schedule, "Created", task.createdAt.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Button(onClick = onEdit, modifier = Modifier.fillMaxWidth()) {
|
|
83
|
+
Icon(Icons.Outlined.Edit, contentDescription = null)
|
|
84
|
+
Spacer(Modifier.width(8.dp))
|
|
85
|
+
Text("Edit task")
|
|
86
|
+
}
|
|
87
|
+
OutlinedButton(onClick = onToggleStatus, modifier = Modifier.fillMaxWidth()) {
|
|
88
|
+
Text(if (task.status.name == "Done") "Reopen task" else "Mark complete")
|
|
89
|
+
}
|
|
90
|
+
OutlinedButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) {
|
|
91
|
+
Icon(Icons.Outlined.Delete, contentDescription = null)
|
|
92
|
+
Spacer(Modifier.width(8.dp))
|
|
93
|
+
Text("Delete task")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Composable
|
|
99
|
+
private fun InlineVideoPlayer(url: String) {
|
|
100
|
+
val context = LocalContext.current
|
|
101
|
+
AndroidView(
|
|
102
|
+
modifier = Modifier.fillMaxWidth().height(220.dp),
|
|
103
|
+
factory = {
|
|
104
|
+
VideoView(context).apply {
|
|
105
|
+
setVideoURI(Uri.parse(url))
|
|
106
|
+
setMediaController(MediaController(context).also { controller -> controller.setAnchorView(this) })
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
update = { if (!it.isPlaying) it.seekTo(1) }
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.sheets
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.Row
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.material3.Button
|
|
9
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
10
|
+
import androidx.compose.material3.ModalBottomSheet
|
|
11
|
+
import androidx.compose.material3.OutlinedTextField
|
|
12
|
+
import androidx.compose.material3.Text
|
|
13
|
+
import androidx.compose.runtime.Composable
|
|
14
|
+
import androidx.compose.runtime.mutableStateOf
|
|
15
|
+
import androidx.compose.runtime.remember
|
|
16
|
+
import androidx.compose.ui.Modifier
|
|
17
|
+
import androidx.compose.ui.unit.dp
|
|
18
|
+
import uz.rsteam.taskflow.model.NewProjectDraft
|
|
19
|
+
import uz.rsteam.taskflow.model.Priority
|
|
20
|
+
import uz.rsteam.taskflow.model.TaskEditorDraft
|
|
21
|
+
import uz.rsteam.taskflow.model.User
|
|
22
|
+
|
|
23
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
24
|
+
@Composable
|
|
25
|
+
fun TaskEditorSheet(
|
|
26
|
+
title: String,
|
|
27
|
+
initial: TaskEditorDraft,
|
|
28
|
+
onDismiss: () -> Unit,
|
|
29
|
+
onSubmit: (TaskEditorDraft) -> Unit
|
|
30
|
+
) {
|
|
31
|
+
val draft = remember { mutableStateOf(initial) }
|
|
32
|
+
ModalBottomSheet(onDismissRequest = onDismiss) {
|
|
33
|
+
Column(modifier = Modifier.fillMaxWidth().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
34
|
+
Text(title)
|
|
35
|
+
OutlinedTextField(value = draft.value.title, onValueChange = { draft.value = draft.value.copy(title = it) }, label = { Text("Title") })
|
|
36
|
+
OutlinedTextField(value = draft.value.description, onValueChange = { draft.value = draft.value.copy(description = it) }, label = { Text("Description") })
|
|
37
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
38
|
+
Button(onClick = onDismiss, modifier = Modifier.weight(1f)) { Text("Cancel") }
|
|
39
|
+
Button(onClick = { onSubmit(draft.value) }, modifier = Modifier.weight(1f), enabled = draft.value.title.trim().length >= 3) {
|
|
40
|
+
Text("Save")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
48
|
+
@Composable
|
|
49
|
+
fun NewProjectSheet(onDismiss: () -> Unit, onCreate: (NewProjectDraft) -> Unit) {
|
|
50
|
+
val draft = remember { mutableStateOf(NewProjectDraft()) }
|
|
51
|
+
ModalBottomSheet(onDismissRequest = onDismiss) {
|
|
52
|
+
Column(modifier = Modifier.fillMaxWidth().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
53
|
+
Text("New project")
|
|
54
|
+
OutlinedTextField(value = draft.value.name, onValueChange = { draft.value = draft.value.copy(name = it) }, label = { Text("Project name") })
|
|
55
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
56
|
+
Button(onClick = onDismiss, modifier = Modifier.weight(1f)) { Text("Cancel") }
|
|
57
|
+
Button(onClick = { onCreate(draft.value) }, modifier = Modifier.weight(1f), enabled = draft.value.name.isNotBlank()) { Text("Create") }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
64
|
+
@Composable
|
|
65
|
+
fun AssignUserSheet(users: List<User>, onDismiss: () -> Unit, onAssign: (User) -> Unit) {
|
|
66
|
+
ModalBottomSheet(onDismissRequest = onDismiss) {
|
|
67
|
+
Column(modifier = Modifier.fillMaxWidth().padding(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
68
|
+
Text("Assign to")
|
|
69
|
+
users.forEach { user ->
|
|
70
|
+
Button(onClick = { onAssign(user) }, modifier = Modifier.fillMaxWidth()) {
|
|
71
|
+
Text(user.name)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
Button(onClick = onDismiss, modifier = Modifier.fillMaxWidth()) { Text("Cancel") }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.ui.theme
|
|
2
|
+
|
|
3
|
+
import androidx.compose.ui.graphics.Color
|
|
4
|
+
|
|
5
|
+
val BrandPrimary = Color(0xFF5B4FE8)
|
|
6
|
+
val BrandSecondary = Color(0xFFE8634F)
|
|
7
|
+
val SurfacePrimary = Color(0xFFFFFFFF)
|
|
8
|
+
val SurfaceSecondary = Color(0xFFF7F6F3)
|
|
9
|
+
val SurfaceTertiary = Color(0xFFEFEEE8)
|
|
10
|
+
val TextPrimary = Color(0xFF1C1B1A)
|
|
11
|
+
val TextSecondary = Color(0xFF6B6966)
|
|
12
|
+
val TextTertiary = Color(0xFF9C9A94)
|
|
13
|
+
val Success = Color(0xFF2D9D5E)
|
|
14
|
+
val Warning = Color(0xFFD4920E)
|
|
15
|
+
val Danger = Color(0xFFD43B3B)
|
|
16
|
+
val Info = Color(0xFF3B82D4)
|
|
17
|
+
val PriorityLow = Color(0xFF9CA3AF)
|
|
18
|
+
val PriorityMedium = Color(0xFF3B82D4)
|
|
19
|
+
val PriorityHigh = Color(0xFFD4920E)
|
|
20
|
+
val PriorityUrgent = Color(0xFFD43B3B)
|
|
21
|
+
|
|
22
|
+
val DarkSurfacePrimary = Color(0xFF171515)
|
|
23
|
+
val DarkSurfaceSecondary = Color(0xFF221F1F)
|
|
24
|
+
val DarkSurfaceTertiary = Color(0xFF2B2727)
|
|
25
|
+
val DarkTextPrimary = Color(0xFFF6F2EF)
|
|
26
|
+
val DarkTextSecondary = Color(0xFFD2CBC6)
|
|
27
|
+
val DarkTextTertiary = Color(0xFFA39891)
|
|
28
|
+
val WarmSurfacePrimary = Color(0xFFF9F6F0)
|
|
29
|
+
val WarmSurfaceSecondary = Color(0xFFF2ECE1)
|
|
30
|
+
val WarmSurfaceTertiary = Color(0xFFEAE0D2)
|