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,112 @@
|
|
|
1
|
+
<!-- openuispec-rules-start -->
|
|
2
|
+
<!-- openuispec-rules-version: 0.1.28 -->
|
|
3
|
+
# OpenUISpec — AI Assistant Rules
|
|
4
|
+
# ================================
|
|
5
|
+
# This project uses OpenUISpec to define UI as a semantic spec.
|
|
6
|
+
# Spec files are the single source of truth for all UI across platforms.
|
|
7
|
+
# Targets: "ios", "android", "web"
|
|
8
|
+
|
|
9
|
+
## IMPORTANT — Read the specification before working with spec files
|
|
10
|
+
|
|
11
|
+
The spec format, file schemas, and generation rules are defined in the installed `openuispec` package.
|
|
12
|
+
You MUST read the reference files listed below before creating, editing, or generating from any spec file.
|
|
13
|
+
Do NOT guess the file format — skipping this step will produce invalid YAML that fails validation.
|
|
14
|
+
|
|
15
|
+
**Find the package in this order:**
|
|
16
|
+
1. `node_modules/openuispec/` (project dependency)
|
|
17
|
+
2. Run `npm root -g` → `<prefix>/openuispec/` (global install)
|
|
18
|
+
3. Online: `https://openuispec.rsteam.uz/llms-full.txt` (if not installed)
|
|
19
|
+
|
|
20
|
+
**Reference files inside the package (read in this order):**
|
|
21
|
+
1. `README.md` — schema tables, file format reference, root wrapper keys
|
|
22
|
+
2. `spec/openuispec-v0.1.md` — full specification (contracts, layout, expressions, adaptive, etc.)
|
|
23
|
+
3. `examples/taskflow/openuispec/` — complete working example with all file types
|
|
24
|
+
4. `schema/` — JSON Schemas for every file type
|
|
25
|
+
|
|
26
|
+
These files are updated with each package version. Always read from the installed package,
|
|
27
|
+
not from cached or memorized content, to ensure you use the latest spec.
|
|
28
|
+
|
|
29
|
+
## What is OpenUISpec
|
|
30
|
+
OpenUISpec is a YAML-based spec format that describes an app's UI semantically — tokens, screens, flows, and platform overrides. AI reads the spec and generates native code (SwiftUI, Compose, React). AI reads native code and updates the spec. The spec is the sync layer between platforms.
|
|
31
|
+
|
|
32
|
+
## Spec location
|
|
33
|
+
- Spec root: `openuispec/`
|
|
34
|
+
- Manifest: `openuispec/openuispec.yaml` — always read this first.
|
|
35
|
+
- Tokens: `openuispec/tokens/`
|
|
36
|
+
- Screens: `openuispec/screens/`
|
|
37
|
+
- Flows: `openuispec/flows/`
|
|
38
|
+
- Contracts: `openuispec/contracts/`
|
|
39
|
+
- Platform: `openuispec/platform/`
|
|
40
|
+
- Locales: `openuispec/locales/`
|
|
41
|
+
|
|
42
|
+
**Note:** These are the default paths. Actual paths are in `includes:` in `openuispec.yaml` and may use relative paths. Always read `openuispec.yaml` to find the real directories.
|
|
43
|
+
|
|
44
|
+
## If spec directories are empty (first-time setup)
|
|
45
|
+
This means the project has existing UI code but hasn't been specced yet. Your job:
|
|
46
|
+
|
|
47
|
+
1. **Read the spec first** — find and read `spec/openuispec-v0.1.md` from the installed package.
|
|
48
|
+
2. **Find existing screens** — scan the codebase for UI screen files.
|
|
49
|
+
3. **Create stubs** — for each screen, create `openuispec/screens/<name>.yaml` with:
|
|
50
|
+
```yaml
|
|
51
|
+
screen_name:
|
|
52
|
+
semantic: "Brief description of what this screen does"
|
|
53
|
+
status: stub
|
|
54
|
+
layout:
|
|
55
|
+
type: scroll_vertical
|
|
56
|
+
```
|
|
57
|
+
4. **Extract tokens** — scan for colors, fonts, spacing and create files in `openuispec/tokens/`.
|
|
58
|
+
5. **Update the manifest** — fill in `data_model` and `api.endpoints` in `openuispec/openuispec.yaml`.
|
|
59
|
+
|
|
60
|
+
## OpenUISpec Source Of Truth
|
|
61
|
+
|
|
62
|
+
OpenUISpec spec files are the primary source of truth for UI behavior across platforms.
|
|
63
|
+
|
|
64
|
+
### Start from spec when:
|
|
65
|
+
- the request changes screen structure
|
|
66
|
+
- the request changes navigation
|
|
67
|
+
- the request changes fields, actions, validation, or data binding
|
|
68
|
+
- the request changes tokens, variants, contracts, flows, or localization
|
|
69
|
+
- the request affects more than one platform
|
|
70
|
+
- the request is phrased in product/UI terms rather than platform-code terms
|
|
71
|
+
|
|
72
|
+
Spec-first workflow:
|
|
73
|
+
1. Read `openuispec/openuispec.yaml` and the relevant spec files first.
|
|
74
|
+
2. Update the spec first.
|
|
75
|
+
3. Update the affected generated/native UI code to match the spec.
|
|
76
|
+
4. Run `openuispec validate`.
|
|
77
|
+
5. Run `openuispec validate semantic`.
|
|
78
|
+
6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
|
|
79
|
+
7. Run `openuispec prepare --target <target>` to build the AI/developer work bundle for that target.
|
|
80
|
+
8. Verify the affected UI targets build/run if possible.
|
|
81
|
+
9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
|
|
82
|
+
10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
|
|
83
|
+
11. Use `openuispec status` to see which other targets are still behind the updated spec.
|
|
84
|
+
|
|
85
|
+
### Start from platform code when:
|
|
86
|
+
- the change is platform-specific polish
|
|
87
|
+
- the change is a local bug fix that does not alter shared semantic behavior
|
|
88
|
+
- the request explicitly asks for an iOS-only, Android-only, or web-only adjustment
|
|
89
|
+
|
|
90
|
+
Platform-first workflow:
|
|
91
|
+
1. Update native/platform code.
|
|
92
|
+
2. If the change affects shared semantics, sync the spec afterward.
|
|
93
|
+
3. If the change is intentionally platform-specific, document it in `platform/*.yaml` when appropriate.
|
|
94
|
+
|
|
95
|
+
### Never do this:
|
|
96
|
+
- Do not snapshot drift immediately after changing spec unless the UI code has also been updated.
|
|
97
|
+
- Do not treat `openuispec drift` as proof that generated UI matches the spec.
|
|
98
|
+
- Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
|
|
99
|
+
- Do not modify generated UI without checking whether the spec must change first.
|
|
100
|
+
|
|
101
|
+
## CLI commands
|
|
102
|
+
- `openuispec init` — scaffold a new spec project
|
|
103
|
+
- `openuispec validate [group...]` — validate spec files against schemas
|
|
104
|
+
- `openuispec validate semantic` — run semantic cross-reference linting
|
|
105
|
+
- `openuispec drift --target <t>` — check for spec drift
|
|
106
|
+
- `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
|
|
107
|
+
- `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
|
|
108
|
+
- `openuispec prepare --target <t>` — build an AI-ready target update bundle
|
|
109
|
+
- `openuispec status` — show cross-target baseline/drift status
|
|
110
|
+
- `openuispec update-rules` — update AI rules to match installed package version
|
|
111
|
+
- `openuispec drift --all` — include stubs in drift check
|
|
112
|
+
<!-- openuispec-rules-end -->
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# TaskFlow Android Target
|
|
2
|
+
|
|
3
|
+
This Android target was generated directly from the local `openuispec/` TaskFlow specification, without copying another sample project's output.
|
|
4
|
+
|
|
5
|
+
## What is included
|
|
6
|
+
|
|
7
|
+
- A lean Jetpack Compose app scaffold under `app/`
|
|
8
|
+
- Adaptive navigation using spec breakpoints:
|
|
9
|
+
- `compact`: bottom navigation
|
|
10
|
+
- `regular` and `expanded`: navigation rail
|
|
11
|
+
- Spec-derived screens:
|
|
12
|
+
- Home
|
|
13
|
+
- Task detail
|
|
14
|
+
- Projects
|
|
15
|
+
- Project detail
|
|
16
|
+
- Calendar stub
|
|
17
|
+
- Settings
|
|
18
|
+
- Profile edit
|
|
19
|
+
- Spec-derived flows:
|
|
20
|
+
- Create task
|
|
21
|
+
- Edit task
|
|
22
|
+
- Assign task
|
|
23
|
+
- New project
|
|
24
|
+
- Local sample data matching the spec's data model
|
|
25
|
+
- English string resources derived from `openuispec/locales/en.json`
|
|
26
|
+
- Theme colors based on `openuispec/tokens/color.yaml` and `themes.yaml`
|
|
27
|
+
|
|
28
|
+
## Project notes
|
|
29
|
+
|
|
30
|
+
- This is intentionally lean. It favors clear generated structure over a fully productionized architecture.
|
|
31
|
+
- The app uses local Compose state instead of wiring real API clients.
|
|
32
|
+
- The media-player custom contract is acknowledged in the spec but not implemented here because no screen in this minimal target renders a concrete media attachment asset.
|
|
33
|
+
- No Gradle wrapper is checked in. Use a local Gradle installation or import the project into Android Studio and let it create the wrapper if needed.
|
|
34
|
+
|
|
35
|
+
## Suggested verification
|
|
36
|
+
|
|
37
|
+
From this directory:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gradle :app:assembleDebug
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or open `generated/android/TaskFlow` in Android Studio and run the app on an emulator.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id("com.android.application")
|
|
3
|
+
id("org.jetbrains.kotlin.android")
|
|
4
|
+
id("org.jetbrains.kotlin.plugin.compose")
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace = "uz.rsteam.taskflow"
|
|
11
|
+
compileSdk = 35
|
|
12
|
+
|
|
13
|
+
defaultConfig {
|
|
14
|
+
applicationId = "uz.rsteam.taskflow"
|
|
15
|
+
minSdk = 26
|
|
16
|
+
targetSdk = 35
|
|
17
|
+
versionCode = 1
|
|
18
|
+
versionName = "1.0.0"
|
|
19
|
+
|
|
20
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
21
|
+
vectorDrawables {
|
|
22
|
+
useSupportLibrary = true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
buildTypes {
|
|
27
|
+
release {
|
|
28
|
+
isMinifyEnabled = false
|
|
29
|
+
proguardFiles(
|
|
30
|
+
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
31
|
+
"proguard-rules.pro"
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
compileOptions {
|
|
37
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
38
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
buildFeatures {
|
|
42
|
+
compose = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
packaging {
|
|
46
|
+
resources {
|
|
47
|
+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
kotlin {
|
|
53
|
+
compilerOptions {
|
|
54
|
+
jvmTarget.set(JvmTarget.JVM_17)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dependencies {
|
|
59
|
+
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
|
|
60
|
+
|
|
61
|
+
implementation(composeBom)
|
|
62
|
+
androidTestImplementation(composeBom)
|
|
63
|
+
|
|
64
|
+
implementation("androidx.core:core-ktx:1.15.0")
|
|
65
|
+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
|
|
66
|
+
implementation("androidx.activity:activity-compose:1.9.3")
|
|
67
|
+
implementation("androidx.compose.ui:ui")
|
|
68
|
+
implementation("androidx.compose.ui:ui-graphics")
|
|
69
|
+
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
70
|
+
implementation("androidx.compose.material3:material3")
|
|
71
|
+
implementation("androidx.compose.material:material-icons-extended")
|
|
72
|
+
implementation("androidx.compose.foundation:foundation")
|
|
73
|
+
|
|
74
|
+
debugImplementation("androidx.compose.ui:ui-tooling")
|
|
75
|
+
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Generated sample app. No custom ProGuard rules required.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<application
|
|
5
|
+
android:allowBackup="true"
|
|
6
|
+
android:label="@string/app_name"
|
|
7
|
+
android:supportsRtl="true"
|
|
8
|
+
android:theme="@style/Theme.TaskFlow">
|
|
9
|
+
<activity
|
|
10
|
+
android:name=".MainActivity"
|
|
11
|
+
android:exported="true"
|
|
12
|
+
android:theme="@style/Theme.TaskFlow">
|
|
13
|
+
<intent-filter>
|
|
14
|
+
<action android:name="android.intent.action.MAIN" />
|
|
15
|
+
|
|
16
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
17
|
+
</intent-filter>
|
|
18
|
+
</activity>
|
|
19
|
+
</application>
|
|
20
|
+
|
|
21
|
+
</manifest>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package uz.rsteam.taskflow
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import androidx.activity.ComponentActivity
|
|
5
|
+
import androidx.activity.compose.setContent
|
|
6
|
+
import androidx.activity.enableEdgeToEdge
|
|
7
|
+
import uz.rsteam.taskflow.ui.theme.TaskFlowTheme
|
|
8
|
+
|
|
9
|
+
class MainActivity : ComponentActivity() {
|
|
10
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
11
|
+
super.onCreate(savedInstanceState)
|
|
12
|
+
enableEdgeToEdge()
|
|
13
|
+
setContent {
|
|
14
|
+
TaskFlowTheme {
|
|
15
|
+
TaskFlowApp()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
package uz.rsteam.taskflow
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
4
|
+
import androidx.compose.foundation.layout.Row
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxHeight
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
8
|
+
import androidx.compose.material.icons.Icons
|
|
9
|
+
import androidx.compose.material.icons.outlined.CalendarMonth
|
|
10
|
+
import androidx.compose.material.icons.outlined.Folder
|
|
11
|
+
import androidx.compose.material.icons.outlined.Home
|
|
12
|
+
import androidx.compose.material.icons.outlined.Settings
|
|
13
|
+
import androidx.compose.material3.FloatingActionButton
|
|
14
|
+
import androidx.compose.material3.Icon
|
|
15
|
+
import androidx.compose.material3.MaterialTheme
|
|
16
|
+
import androidx.compose.material3.NavigationBar
|
|
17
|
+
import androidx.compose.material3.NavigationBarItem
|
|
18
|
+
import androidx.compose.material3.NavigationRail
|
|
19
|
+
import androidx.compose.material3.NavigationRailItem
|
|
20
|
+
import androidx.compose.material3.Scaffold
|
|
21
|
+
import androidx.compose.material3.Text
|
|
22
|
+
import androidx.compose.runtime.Composable
|
|
23
|
+
import androidx.compose.runtime.getValue
|
|
24
|
+
import androidx.compose.runtime.mutableStateListOf
|
|
25
|
+
import androidx.compose.runtime.mutableStateOf
|
|
26
|
+
import androidx.compose.runtime.remember
|
|
27
|
+
import androidx.compose.runtime.setValue
|
|
28
|
+
import androidx.compose.ui.Modifier
|
|
29
|
+
import androidx.compose.ui.unit.dp
|
|
30
|
+
import uz.rsteam.taskflow.model.NewProjectDraft
|
|
31
|
+
import uz.rsteam.taskflow.model.Priority
|
|
32
|
+
import uz.rsteam.taskflow.model.Project
|
|
33
|
+
import uz.rsteam.taskflow.model.Route
|
|
34
|
+
import uz.rsteam.taskflow.model.SampleData
|
|
35
|
+
import uz.rsteam.taskflow.model.SheetState
|
|
36
|
+
import uz.rsteam.taskflow.model.Task
|
|
37
|
+
import uz.rsteam.taskflow.model.TaskEditorDraft
|
|
38
|
+
import uz.rsteam.taskflow.model.TaskFilter
|
|
39
|
+
import uz.rsteam.taskflow.model.TaskStatus
|
|
40
|
+
import uz.rsteam.taskflow.model.TopLevelScreen
|
|
41
|
+
import uz.rsteam.taskflow.ui.screens.CalendarScreen
|
|
42
|
+
import uz.rsteam.taskflow.ui.screens.HomeScreen
|
|
43
|
+
import uz.rsteam.taskflow.ui.screens.ProfileEditScreen
|
|
44
|
+
import uz.rsteam.taskflow.ui.screens.ProjectDetailScreen
|
|
45
|
+
import uz.rsteam.taskflow.ui.screens.ProjectsScreen
|
|
46
|
+
import uz.rsteam.taskflow.ui.screens.SettingsScreen
|
|
47
|
+
import uz.rsteam.taskflow.ui.screens.TaskDetailScreen
|
|
48
|
+
import uz.rsteam.taskflow.ui.sheets.AssignUserSheet
|
|
49
|
+
import uz.rsteam.taskflow.ui.sheets.NewProjectSheet
|
|
50
|
+
import uz.rsteam.taskflow.ui.sheets.TaskEditorSheet
|
|
51
|
+
import uz.rsteam.taskflow.ui.theme.TaskFlowTheme
|
|
52
|
+
import java.time.LocalDate
|
|
53
|
+
import java.time.LocalDateTime
|
|
54
|
+
import java.util.UUID
|
|
55
|
+
|
|
56
|
+
@Composable
|
|
57
|
+
fun TaskFlowApp() {
|
|
58
|
+
val users = remember { SampleData.users }
|
|
59
|
+
val currentUser = users.first()
|
|
60
|
+
val projects = remember { mutableStateListOf<Project>().apply { addAll(SampleData.projects) } }
|
|
61
|
+
val tasks = remember { mutableStateListOf<Task>().apply { addAll(SampleData.tasks(currentUser)) } }
|
|
62
|
+
|
|
63
|
+
var route by remember { mutableStateOf<Route>(Route.TopLevel(TopLevelScreen.Home)) }
|
|
64
|
+
var sheetState by remember { mutableStateOf<SheetState?>(null) }
|
|
65
|
+
var searchQuery by remember { mutableStateOf("") }
|
|
66
|
+
var activeFilter by remember { mutableStateOf(TaskFilter.Today) }
|
|
67
|
+
|
|
68
|
+
TaskFlowTheme {
|
|
69
|
+
BoxWithConstraints {
|
|
70
|
+
val compact = maxWidth < 600.dp
|
|
71
|
+
val primaryColor = MaterialTheme.colorScheme.primary
|
|
72
|
+
val selectedTopLevel = when (val r = route) {
|
|
73
|
+
is Route.TopLevel -> r.screen
|
|
74
|
+
is Route.ProjectDetail -> TopLevelScreen.Projects
|
|
75
|
+
is Route.ProfileEdit -> TopLevelScreen.Settings
|
|
76
|
+
is Route.TaskDetail -> TopLevelScreen.Home
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Scaffold(
|
|
80
|
+
containerColor = MaterialTheme.colorScheme.background,
|
|
81
|
+
bottomBar = {
|
|
82
|
+
if (compact) {
|
|
83
|
+
NavigationBar {
|
|
84
|
+
TopLevelScreen.entries.forEach { item ->
|
|
85
|
+
NavigationBarItem(
|
|
86
|
+
selected = item == selectedTopLevel,
|
|
87
|
+
onClick = { route = Route.TopLevel(item) },
|
|
88
|
+
icon = {
|
|
89
|
+
Icon(
|
|
90
|
+
when (item) {
|
|
91
|
+
TopLevelScreen.Home -> Icons.Outlined.Home
|
|
92
|
+
TopLevelScreen.Projects -> Icons.Outlined.Folder
|
|
93
|
+
TopLevelScreen.Calendar -> Icons.Outlined.CalendarMonth
|
|
94
|
+
TopLevelScreen.Settings -> Icons.Outlined.Settings
|
|
95
|
+
},
|
|
96
|
+
contentDescription = null
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
label = { Text(textFor(item)) }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
floatingActionButton = {
|
|
106
|
+
if (selectedTopLevel == TopLevelScreen.Home) {
|
|
107
|
+
FloatingActionButton(onClick = { sheetState = SheetState.CreateTask }) {
|
|
108
|
+
Text("+")
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
) { innerPadding ->
|
|
113
|
+
Row(modifier = Modifier.fillMaxSize()) {
|
|
114
|
+
if (!compact) {
|
|
115
|
+
NavigationRail(modifier = Modifier.fillMaxHeight().navigationBarsPadding()) {
|
|
116
|
+
TopLevelScreen.entries.forEach { item ->
|
|
117
|
+
NavigationRailItem(
|
|
118
|
+
selected = item == selectedTopLevel,
|
|
119
|
+
onClick = { route = Route.TopLevel(item) },
|
|
120
|
+
icon = {
|
|
121
|
+
Icon(
|
|
122
|
+
when (item) {
|
|
123
|
+
TopLevelScreen.Home -> Icons.Outlined.Home
|
|
124
|
+
TopLevelScreen.Projects -> Icons.Outlined.Folder
|
|
125
|
+
TopLevelScreen.Calendar -> Icons.Outlined.CalendarMonth
|
|
126
|
+
TopLevelScreen.Settings -> Icons.Outlined.Settings
|
|
127
|
+
},
|
|
128
|
+
contentDescription = null
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
label = { Text(textFor(item)) }
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
when (val currentRoute = route) {
|
|
138
|
+
is Route.TopLevel -> when (currentRoute.screen) {
|
|
139
|
+
TopLevelScreen.Home -> HomeScreen(
|
|
140
|
+
currentUser = currentUser,
|
|
141
|
+
projects = projects,
|
|
142
|
+
tasks = tasks,
|
|
143
|
+
activeFilter = activeFilter,
|
|
144
|
+
searchQuery = searchQuery,
|
|
145
|
+
onSearchChange = { searchQuery = it },
|
|
146
|
+
onFilterChange = { activeFilter = it },
|
|
147
|
+
onSelectTask = { route = Route.TaskDetail(it) },
|
|
148
|
+
onCreateTask = { sheetState = SheetState.CreateTask }
|
|
149
|
+
)
|
|
150
|
+
TopLevelScreen.Projects -> ProjectsScreen(
|
|
151
|
+
projects = projects.map { it.copy(taskCount = tasks.count { t -> t.projectId == it.id }) },
|
|
152
|
+
onOpenProject = { route = Route.ProjectDetail(it) },
|
|
153
|
+
onNewProject = { sheetState = SheetState.NewProject }
|
|
154
|
+
)
|
|
155
|
+
TopLevelScreen.Calendar -> CalendarScreen()
|
|
156
|
+
TopLevelScreen.Settings -> SettingsScreen(
|
|
157
|
+
currentUser = currentUser,
|
|
158
|
+
preferences = uz.rsteam.taskflow.model.Preferences(),
|
|
159
|
+
onPreferencesChange = {},
|
|
160
|
+
onEditProfile = { route = Route.ProfileEdit }
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
is Route.TaskDetail -> {
|
|
164
|
+
val task = tasks.firstOrNull { it.id == currentRoute.taskId }
|
|
165
|
+
if (task != null) {
|
|
166
|
+
TaskDetailScreen(
|
|
167
|
+
task = task,
|
|
168
|
+
project = projects.firstOrNull { it.id == task.projectId },
|
|
169
|
+
onEdit = { sheetState = SheetState.EditTask(task.id) },
|
|
170
|
+
onToggleStatus = {
|
|
171
|
+
val idx = tasks.indexOfFirst { it.id == task.id }
|
|
172
|
+
if (idx >= 0) tasks[idx] = task.copy(
|
|
173
|
+
status = if (task.status == TaskStatus.Done) TaskStatus.Todo else TaskStatus.Done,
|
|
174
|
+
updatedAt = LocalDateTime.now()
|
|
175
|
+
)
|
|
176
|
+
},
|
|
177
|
+
onDelete = {
|
|
178
|
+
tasks.removeAll { it.id == task.id }
|
|
179
|
+
route = Route.TopLevel(TopLevelScreen.Home)
|
|
180
|
+
},
|
|
181
|
+
onProjectClick = { task.projectId?.let { route = Route.ProjectDetail(it) } },
|
|
182
|
+
onAssign = { sheetState = SheetState.AssignTask(task.id) }
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
is Route.ProjectDetail -> {
|
|
187
|
+
val project = projects.firstOrNull { it.id == currentRoute.projectId }
|
|
188
|
+
if (project != null) {
|
|
189
|
+
ProjectDetailScreen(project = project, tasks = tasks.filter { it.projectId == project.id }, onOpenTask = {
|
|
190
|
+
route = Route.TaskDetail(it)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
Route.ProfileEdit -> ProfileEditScreen(currentUser = currentUser) { _, _ ->
|
|
195
|
+
route = Route.TopLevel(TopLevelScreen.Settings)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
when (val sheet = sheetState) {
|
|
202
|
+
SheetState.CreateTask -> TaskEditorSheet(
|
|
203
|
+
title = "New task",
|
|
204
|
+
initial = TaskEditorDraft(priority = Priority.Medium),
|
|
205
|
+
onDismiss = { sheetState = null },
|
|
206
|
+
onSubmit = { draft ->
|
|
207
|
+
tasks.add(0, toTask(draft, currentUser))
|
|
208
|
+
sheetState = null
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
is SheetState.EditTask -> {
|
|
212
|
+
val task = tasks.firstOrNull { it.id == sheet.taskId }
|
|
213
|
+
if (task != null) {
|
|
214
|
+
TaskEditorSheet(
|
|
215
|
+
title = "Edit task",
|
|
216
|
+
initial = TaskEditorDraft(
|
|
217
|
+
title = task.title,
|
|
218
|
+
description = task.description ?: "",
|
|
219
|
+
projectId = task.projectId,
|
|
220
|
+
priority = task.priority,
|
|
221
|
+
dueDate = task.dueDate?.toString() ?: "",
|
|
222
|
+
tags = task.tags.joinToString(", "),
|
|
223
|
+
assignToSelf = task.assignee?.id == currentUser.id
|
|
224
|
+
),
|
|
225
|
+
onDismiss = { sheetState = null },
|
|
226
|
+
onSubmit = { draft ->
|
|
227
|
+
val idx = tasks.indexOfFirst { it.id == task.id }
|
|
228
|
+
if (idx >= 0) tasks[idx] = task.copy(
|
|
229
|
+
title = draft.title,
|
|
230
|
+
description = draft.description.ifBlank { null },
|
|
231
|
+
priority = draft.priority,
|
|
232
|
+
dueDate = draft.dueDate.toLocalDateOrNull(),
|
|
233
|
+
projectId = draft.projectId,
|
|
234
|
+
tags = draft.tags.toTags(),
|
|
235
|
+
assignee = if (draft.assignToSelf) currentUser else null,
|
|
236
|
+
updatedAt = LocalDateTime.now()
|
|
237
|
+
)
|
|
238
|
+
sheetState = null
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
SheetState.NewProject -> NewProjectSheet(
|
|
244
|
+
onDismiss = { sheetState = null },
|
|
245
|
+
onCreate = { draft: NewProjectDraft ->
|
|
246
|
+
projects.add(Project(UUID.randomUUID().toString(), draft.name.trim(), primaryColor, Icons.Outlined.Folder, 0))
|
|
247
|
+
sheetState = null
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
is SheetState.AssignTask -> AssignUserSheet(users = users, onDismiss = { sheetState = null }) { user ->
|
|
251
|
+
val idx = tasks.indexOfFirst { it.id == sheet.taskId }
|
|
252
|
+
if (idx >= 0) tasks[idx] = tasks[idx].copy(assignee = user, updatedAt = LocalDateTime.now())
|
|
253
|
+
sheetState = null
|
|
254
|
+
}
|
|
255
|
+
null -> Unit
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private fun textFor(screen: TopLevelScreen) = when (screen) {
|
|
262
|
+
TopLevelScreen.Home -> "Tasks"
|
|
263
|
+
TopLevelScreen.Projects -> "Projects"
|
|
264
|
+
TopLevelScreen.Calendar -> "Calendar"
|
|
265
|
+
TopLevelScreen.Settings -> "Settings"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun toTask(draft: TaskEditorDraft, currentUser: uz.rsteam.taskflow.model.User): Task = Task(
|
|
269
|
+
id = UUID.randomUUID().toString(),
|
|
270
|
+
title = draft.title.trim(),
|
|
271
|
+
description = draft.description.trim().ifBlank { null },
|
|
272
|
+
status = TaskStatus.Todo,
|
|
273
|
+
priority = draft.priority,
|
|
274
|
+
dueDate = draft.dueDate.toLocalDateOrNull(),
|
|
275
|
+
projectId = draft.projectId,
|
|
276
|
+
tags = draft.tags.toTags(),
|
|
277
|
+
createdAt = LocalDateTime.now(),
|
|
278
|
+
updatedAt = LocalDateTime.now(),
|
|
279
|
+
assignee = if (draft.assignToSelf) currentUser else null
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
private fun String.toLocalDateOrNull(): LocalDate? = runCatching { LocalDate.parse(this) }.getOrNull()
|
|
283
|
+
private fun String.toTags(): List<String> = split(",").map { it.trim() }.filter { it.isNotBlank() }
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package uz.rsteam.taskflow.model
|
|
2
|
+
|
|
3
|
+
import androidx.compose.ui.graphics.Color
|
|
4
|
+
import androidx.compose.ui.graphics.vector.ImageVector
|
|
5
|
+
import uz.rsteam.taskflow.R
|
|
6
|
+
import java.time.LocalDate
|
|
7
|
+
import java.time.LocalDateTime
|
|
8
|
+
|
|
9
|
+
enum class TopLevelScreen(val titleRes: Int) {
|
|
10
|
+
Home(R.string.nav_tasks),
|
|
11
|
+
Projects(R.string.nav_projects),
|
|
12
|
+
Calendar(R.string.nav_calendar),
|
|
13
|
+
Settings(R.string.nav_settings)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
sealed interface Route {
|
|
17
|
+
data class TopLevel(val screen: TopLevelScreen) : Route
|
|
18
|
+
data class TaskDetail(val taskId: String) : Route
|
|
19
|
+
data class ProjectDetail(val projectId: String) : Route
|
|
20
|
+
data object ProfileEdit : Route
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
sealed interface SheetState {
|
|
24
|
+
data object CreateTask : SheetState
|
|
25
|
+
data class EditTask(val taskId: String) : SheetState
|
|
26
|
+
data object NewProject : SheetState
|
|
27
|
+
data class AssignTask(val taskId: String) : SheetState
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum class TaskFilter(val labelRes: Int) {
|
|
31
|
+
All(R.string.home_filter_all),
|
|
32
|
+
Today(R.string.home_filter_today),
|
|
33
|
+
Upcoming(R.string.home_filter_upcoming),
|
|
34
|
+
Done(R.string.home_filter_done)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
enum class TaskStatus(val labelRes: Int) {
|
|
38
|
+
Todo(R.string.status_todo),
|
|
39
|
+
InProgress(R.string.status_in_progress),
|
|
40
|
+
Done(R.string.status_done)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enum class Priority(val labelRes: Int, val color: Color) {
|
|
44
|
+
Low(R.string.priority_low, Color(0xFF9CA3AF)),
|
|
45
|
+
Medium(R.string.priority_medium, Color(0xFF3B82D4)),
|
|
46
|
+
High(R.string.priority_high, Color(0xFFD4920E)),
|
|
47
|
+
Urgent(R.string.priority_urgent, Color(0xFFD43B3B))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
data class User(
|
|
51
|
+
val id: String,
|
|
52
|
+
val name: String,
|
|
53
|
+
val firstName: String,
|
|
54
|
+
val email: String
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
data class Project(
|
|
58
|
+
val id: String,
|
|
59
|
+
val name: String,
|
|
60
|
+
val color: Color,
|
|
61
|
+
val icon: ImageVector,
|
|
62
|
+
val taskCount: Int
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
data class MediaAttachment(
|
|
66
|
+
val source: String,
|
|
67
|
+
val mediaType: String,
|
|
68
|
+
val title: String
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
data class Task(
|
|
72
|
+
val id: String,
|
|
73
|
+
val title: String,
|
|
74
|
+
val description: String?,
|
|
75
|
+
val status: TaskStatus,
|
|
76
|
+
val priority: Priority,
|
|
77
|
+
val dueDate: LocalDate?,
|
|
78
|
+
val projectId: String?,
|
|
79
|
+
val tags: List<String>,
|
|
80
|
+
val createdAt: LocalDateTime,
|
|
81
|
+
val updatedAt: LocalDateTime,
|
|
82
|
+
val assignee: User?,
|
|
83
|
+
val attachment: MediaAttachment? = null
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
data class Preferences(
|
|
87
|
+
val defaultPriority: Priority = Priority.Medium,
|
|
88
|
+
val notificationsEnabled: Boolean = true,
|
|
89
|
+
val remindersEnabled: Boolean = true
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
data class TaskEditorDraft(
|
|
93
|
+
val title: String = "",
|
|
94
|
+
val description: String = "",
|
|
95
|
+
val projectId: String? = null,
|
|
96
|
+
val priority: Priority = Priority.Medium,
|
|
97
|
+
val dueDate: String = "",
|
|
98
|
+
val tags: String = "",
|
|
99
|
+
val assignToSelf: Boolean = true
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
data class NewProjectDraft(
|
|
103
|
+
val name: String = "",
|
|
104
|
+
val colorName: String = "Indigo",
|
|
105
|
+
val iconName: String = "Folder"
|
|
106
|
+
)
|