openuispec 0.2.10 → 0.2.12

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.
Files changed (237) hide show
  1. package/README.md +3 -1
  2. package/check/index.ts +17 -0
  3. package/cli/index.ts +21 -3
  4. package/cli/init.ts +224 -10
  5. package/docs/cli.md +13 -8
  6. package/docs/file-formats.md +36 -0
  7. package/docs/implementation-notes.md +7 -0
  8. package/drift/index.ts +281 -40
  9. package/mcp-server/index.ts +179 -119
  10. package/mcp-server/screenshot.ts +19 -4
  11. package/package.json +5 -2
  12. package/prepare/index.ts +155 -18
  13. package/schema/openuispec.schema.json +59 -0
  14. package/schema/semantic-lint.ts +25 -1
  15. package/scripts/take-all-screenshots.ts +507 -0
  16. package/spec/openuispec-v0.1.md +13 -0
  17. package/status/index.ts +72 -2
  18. package/examples/social-app/.mcp.json +0 -10
  19. package/examples/social-app/AGENTS.md +0 -124
  20. package/examples/social-app/CLAUDE.md +0 -124
  21. package/examples/social-app/backend/.gitkeep +0 -1
  22. package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
  23. package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
  24. package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
  25. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
  26. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
  27. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
  28. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
  29. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
  30. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
  31. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
  32. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
  33. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
  34. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
  35. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
  36. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
  37. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
  38. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
  39. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
  40. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
  41. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
  42. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
  43. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
  44. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
  45. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
  46. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
  47. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
  48. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
  49. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
  50. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
  51. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
  52. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
  53. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
  54. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
  55. package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
  56. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
  57. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
  58. package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
  59. package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
  60. package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
  61. package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
  62. package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
  63. package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
  64. package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
  65. package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
  66. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
  67. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
  68. package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
  69. package/examples/social-app/generated/android/social-app/gradlew +0 -248
  70. package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
  71. package/examples/social-app/generated/web/social-app/index.html +0 -12
  72. package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
  73. package/examples/social-app/generated/web/social-app/package.json +0 -27
  74. package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
  75. package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
  76. package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
  77. package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
  78. package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
  79. package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
  80. package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
  81. package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
  82. package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
  83. package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
  84. package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
  85. package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
  86. package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
  87. package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
  88. package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
  89. package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
  90. package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
  91. package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
  92. package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
  93. package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
  94. package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
  95. package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
  96. package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
  97. package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
  98. package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
  99. package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
  100. package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
  101. package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
  102. package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
  103. package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
  104. package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
  105. package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
  106. package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
  107. package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
  108. package/examples/social-app/package.json +0 -13
  109. package/examples/social-app/take-web-screenshots.ts +0 -97
  110. package/examples/taskflow/.codex/config.toml +0 -4
  111. package/examples/taskflow/.mcp.json +0 -10
  112. package/examples/taskflow/AGENTS.md +0 -124
  113. package/examples/taskflow/CLAUDE.md +0 -124
  114. package/examples/taskflow/backend/.gitkeep +0 -1
  115. package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
  116. package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
  117. package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
  118. package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
  119. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
  120. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
  121. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
  122. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
  123. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
  124. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
  125. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
  126. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
  127. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
  128. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
  129. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
  130. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
  131. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
  132. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
  133. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
  134. package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
  135. package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
  136. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
  137. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
  138. package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
  139. package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
  140. package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
  141. package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
  142. package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
  143. package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
  144. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
  145. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
  146. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
  147. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
  148. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
  149. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
  150. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
  151. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
  152. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
  153. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
  154. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
  155. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
  156. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
  157. package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -31
  158. package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
  159. package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
  160. package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
  161. package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
  162. package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
  163. package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
  164. package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
  165. package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
  166. package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
  167. package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
  168. package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
  169. package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
  170. package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
  171. package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
  172. package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
  173. package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
  174. package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
  175. package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
  176. package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
  177. package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
  178. package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
  179. package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
  180. package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
  181. package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
  182. package/examples/todo-orbit/.codex/config.toml +0 -4
  183. package/examples/todo-orbit/.mcp.json +0 -10
  184. package/examples/todo-orbit/AGENTS.md +0 -124
  185. package/examples/todo-orbit/CLAUDE.md +0 -124
  186. package/examples/todo-orbit/backend/.gitkeep +0 -1
  187. package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
  188. package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
  189. package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
  190. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
  191. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
  192. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
  193. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
  194. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
  195. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
  196. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
  197. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
  198. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
  199. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
  200. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
  201. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
  202. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
  203. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
  204. package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
  205. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
  206. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
  207. package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
  208. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
  209. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
  210. package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
  211. package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
  212. package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
  213. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
  214. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
  215. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
  216. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
  217. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
  218. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
  219. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
  220. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
  221. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
  222. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
  223. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
  224. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
  225. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
  226. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -439
  227. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  228. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/xcshareddata/xcschemes/TodoOrbit.xcscheme +0 -89
  229. package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -32
  230. package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
  231. package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
  232. package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
  233. package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
  234. package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
  235. package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
  236. package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
  237. package/examples/todo-orbit/generated/web/Todo Orbit/vite.config.ts +0 -6
@@ -1,119 +0,0 @@
1
- "nav.tasks" = "Tasks";
2
- "nav.analytics" = "Analytics";
3
- "nav.settings" = "Settings";
4
- "home.title" = "Today, organized";
5
- "home.summary.done" = "Everything is done";
6
- "home.summary.remaining" = "%d tasks left out of %d";
7
- "home.search_placeholder" = "Search by title or notes";
8
- "home.filter_all" = "All";
9
- "home.filter_open" = "Open";
10
- "home.filter_done" = "Done";
11
- "home.new_task" = "New task";
12
- "home.empty_title" = "Nothing to do";
13
- "analytics.title" = "Task analytics";
14
- "analytics.subtitle" = "Monitor throughput, overdue work, and completion trends.";
15
- "analytics.period_week" = "Week";
16
- "analytics.period_month" = "Month";
17
- "analytics.period_quarter" = "Quarter";
18
- "analytics.completed_today" = "Completed today";
19
- "analytics.open_tasks" = "Open tasks";
20
- "analytics.overdue_tasks" = "Overdue";
21
- "analytics.completion_rate" = "Completion rate";
22
- "analytics.trend_subtitle" = "Completion trend";
23
- "analytics.legend_completed" = "Completed";
24
- "analytics.legend_created" = "Created";
25
- "analytics.empty_trend" = "No trend data yet.";
26
- "analytics.overdue_section" = "Overdue review";
27
- "analytics.overdue_subtitle" = "Tasks that need attention first.";
28
- "analytics.empty_overdue_body" = "Everything important is on track.";
29
- "task_detail.title" = "Task details";
30
- "task_detail.status" = "Status";
31
- "task_detail.priority" = "Priority";
32
- "task_detail.notes" = "Notes";
33
- "task_detail.due_date" = "Due date";
34
- "task_detail.no_due_date" = "No deadline";
35
- "task_detail.created" = "Created";
36
- "task_detail.updated" = "Updated";
37
- "task_detail.edit" = "Edit task";
38
- "task_detail.toggle_status" = "Toggle status";
39
- "task_detail.more_info" = "More info";
40
- "task_detail.delete" = "Delete task";
41
- "task_detail.delete_title" = "Delete this task?";
42
- "task_detail.delete_message" = "This action cannot be undone.";
43
- "task_detail.updated_feedback" = "Task updated";
44
- "task_detail.deleted_feedback" = "Task deleted";
45
- "settings.title" = "Preferences";
46
- "settings.subtitle" = "Adjust language and theme for every platform target.";
47
- "settings.language" = "Language";
48
- "settings.language_en" = "English";
49
- "settings.language_ru" = "Russian";
50
- "settings.theme" = "Theme";
51
- "settings.theme_light" = "Light";
52
- "settings.theme_dark" = "Dark";
53
- "settings.reminders" = "Due date reminders";
54
- "settings.daily_summary" = "Daily summary";
55
- "settings.automation_title" = "Automation";
56
- "settings.automation_subtitle" = "Create recurring task rules to stress conditional forms and validation.";
57
- "settings.automation_create_rule" = "Create recurring rule";
58
- "settings.save" = "Save changes";
59
- "settings.saved" = "Preferences updated";
60
- "create_task.title" = "New task";
61
- "create_task.field_title" = "Title";
62
- "create_task.field_notes" = "Notes";
63
- "create_task.field_priority" = "Priority";
64
- "create_task.field_due_date" = "Due date";
65
- "create_task.success" = "Task created";
66
- "edit_task.title" = "Edit task";
67
- "edit_task.field_title" = "Title";
68
- "edit_task.field_notes" = "Notes";
69
- "edit_task.field_priority" = "Priority";
70
- "edit_task.field_due_date" = "Due date";
71
- "edit_task.success" = "Task saved";
72
- "recurring_rule.title" = "Recurring rule";
73
- "recurring_rule.field_name" = "Rule name";
74
- "recurring_rule.field_confirm_name" = "Confirm rule name";
75
- "recurring_rule.field_cadence" = "Cadence";
76
- "recurring_rule.cadence_daily" = "Daily";
77
- "recurring_rule.cadence_weekly" = "Weekly";
78
- "recurring_rule.cadence_monthly" = "Monthly";
79
- "recurring_rule.field_interval" = "Repeat every";
80
- "recurring_rule.field_weekday" = "Weekday";
81
- "recurring_rule.field_month_day" = "Day of month";
82
- "recurring_rule.field_start_date" = "Start date";
83
- "recurring_rule.field_has_end_date" = "Set an end date";
84
- "recurring_rule.field_end_date" = "End date";
85
- "recurring_rule.field_remind_at" = "Reminder time";
86
- "recurring_rule.field_enable_summary" = "Attach daily summary delivery";
87
- "recurring_rule.field_summary_channel" = "Summary channel";
88
- "recurring_rule.summary_push" = "Push notification";
89
- "recurring_rule.summary_email" = "Email";
90
- "recurring_rule.success" = "Recurring rule created";
91
- "recurring_preview.title" = "Upcoming schedule preview";
92
- "recurring_preview.invalid" = "Complete the cadence and date fields to preview the schedule.";
93
- "recurring_preview.empty" = "No upcoming dates can be generated from this rule.";
94
- "priority.low" = "Low";
95
- "priority.medium" = "Medium";
96
- "priority.high" = "High";
97
- "status.open" = "Open";
98
- "status.done" = "Done";
99
- "weekday.mon" = "Monday";
100
- "weekday.tue" = "Tuesday";
101
- "weekday.wed" = "Wednesday";
102
- "weekday.thu" = "Thursday";
103
- "weekday.fri" = "Friday";
104
- "weekday.sat" = "Saturday";
105
- "weekday.sun" = "Sunday";
106
- "validation.min_length" = "Must be at least %d characters";
107
- "validation.min_value" = "Must be at least %d";
108
- "validation.max_value" = "Must be no more than %d";
109
- "validation.rule_name_min_length" = "Rule name must be at least %d characters";
110
- "validation.rule_name_reserved" = "The default name is reserved. Choose a more specific label.";
111
- "validation.rule_name_taken" = "A recurring rule with this name already exists.";
112
- "validation.match_field" = "Fields do not match";
113
- "validation.end_date_after_start" = "End date must be the same as or later than the start date.";
114
- "validation.time_format" = "Use a 24-hour time like 09:00";
115
- "validation.month_day_max" = "Choose a day between 1 and 28";
116
- "validation.fix_errors" = "Fix the highlighted fields before saving.";
117
- "validation.required" = "This field is required.";
118
- "common.cancel" = "Cancel";
119
- "common.delete" = "Delete";
@@ -1,119 +0,0 @@
1
- "nav.tasks" = "Задачи";
2
- "nav.analytics" = "Аналитика";
3
- "nav.settings" = "Настройки";
4
- "home.title" = "Сегодня все под контролем";
5
- "home.summary.done" = "Все задачи закрыты";
6
- "home.summary.remaining" = "Осталось %d из %d";
7
- "home.search_placeholder" = "Искать по названию или заметкам";
8
- "home.filter_all" = "Все";
9
- "home.filter_open" = "Открытые";
10
- "home.filter_done" = "Выполненные";
11
- "home.new_task" = "Новая задача";
12
- "home.empty_title" = "Список пуст";
13
- "analytics.title" = "Аналитика задач";
14
- "analytics.subtitle" = "Следите за выполнением, просроченными задачами и динамикой.";
15
- "analytics.period_week" = "Неделя";
16
- "analytics.period_month" = "Месяц";
17
- "analytics.period_quarter" = "Квартал";
18
- "analytics.completed_today" = "Выполнено сегодня";
19
- "analytics.open_tasks" = "Открытые задачи";
20
- "analytics.overdue_tasks" = "Просрочено";
21
- "analytics.completion_rate" = "Процент выполнения";
22
- "analytics.trend_subtitle" = "Динамика выполнения";
23
- "analytics.legend_completed" = "Выполнено";
24
- "analytics.legend_created" = "Создано";
25
- "analytics.empty_trend" = "Данные тренда пока отсутствуют.";
26
- "analytics.overdue_section" = "Просроченные задачи";
27
- "analytics.overdue_subtitle" = "Задачи, которым нужно уделить внимание в первую очередь.";
28
- "analytics.empty_overdue_body" = "Все важные задачи идут по плану.";
29
- "task_detail.title" = "Детали задачи";
30
- "task_detail.status" = "Статус";
31
- "task_detail.priority" = "Приоритет";
32
- "task_detail.notes" = "Заметки";
33
- "task_detail.due_date" = "Срок";
34
- "task_detail.no_due_date" = "Без срока";
35
- "task_detail.created" = "Создано";
36
- "task_detail.updated" = "Обновлено";
37
- "task_detail.edit" = "Редактировать задачу";
38
- "task_detail.toggle_status" = "Сменить статус";
39
- "task_detail.more_info" = "Подробнее";
40
- "task_detail.delete" = "Удалить задачу";
41
- "task_detail.delete_title" = "Удалить эту задачу?";
42
- "task_detail.delete_message" = "Это действие нельзя отменить.";
43
- "task_detail.updated_feedback" = "Задача обновлена";
44
- "task_detail.deleted_feedback" = "Задача удалена";
45
- "settings.title" = "Параметры";
46
- "settings.subtitle" = "Измените язык и тему для всех целевых платформ.";
47
- "settings.language" = "Язык";
48
- "settings.language_en" = "Английский";
49
- "settings.language_ru" = "Русский";
50
- "settings.theme" = "Тема";
51
- "settings.theme_light" = "Светлая";
52
- "settings.theme_dark" = "Тёмная";
53
- "settings.reminders" = "Напоминания о сроках";
54
- "settings.daily_summary" = "Ежедневная сводка";
55
- "settings.automation_title" = "Автоматизация";
56
- "settings.automation_subtitle" = "Создавайте повторяющиеся правила задач, чтобы проверить условные формы и валидацию.";
57
- "settings.automation_create_rule" = "Создать правило";
58
- "settings.save" = "Сохранить";
59
- "settings.saved" = "Параметры обновлены";
60
- "create_task.title" = "Новая задача";
61
- "create_task.field_title" = "Название";
62
- "create_task.field_notes" = "Заметки";
63
- "create_task.field_priority" = "Приоритет";
64
- "create_task.field_due_date" = "Срок";
65
- "create_task.success" = "Задача создана";
66
- "edit_task.title" = "Редактировать задачу";
67
- "edit_task.field_title" = "Название";
68
- "edit_task.field_notes" = "Заметки";
69
- "edit_task.field_priority" = "Приоритет";
70
- "edit_task.field_due_date" = "Срок";
71
- "edit_task.success" = "Задача сохранена";
72
- "recurring_rule.title" = "Повторяющееся правило";
73
- "recurring_rule.field_name" = "Название правила";
74
- "recurring_rule.field_confirm_name" = "Подтвердите название";
75
- "recurring_rule.field_cadence" = "Периодичность";
76
- "recurring_rule.cadence_daily" = "Ежедневно";
77
- "recurring_rule.cadence_weekly" = "Еженедельно";
78
- "recurring_rule.cadence_monthly" = "Ежемесячно";
79
- "recurring_rule.field_interval" = "Повторять каждые";
80
- "recurring_rule.field_weekday" = "День недели";
81
- "recurring_rule.field_month_day" = "День месяца";
82
- "recurring_rule.field_start_date" = "Дата начала";
83
- "recurring_rule.field_has_end_date" = "Указать дату окончания";
84
- "recurring_rule.field_end_date" = "Дата окончания";
85
- "recurring_rule.field_remind_at" = "Время напоминания";
86
- "recurring_rule.field_enable_summary" = "Добавить ежедневную сводку";
87
- "recurring_rule.field_summary_channel" = "Канал сводки";
88
- "recurring_rule.summary_push" = "Push-уведомление";
89
- "recurring_rule.summary_email" = "Электронная почта";
90
- "recurring_rule.success" = "Повторяющееся правило создано";
91
- "recurring_preview.title" = "Предпросмотр расписания";
92
- "recurring_preview.invalid" = "Заполните периодичность и даты, чтобы увидеть предпросмотр расписания.";
93
- "recurring_preview.empty" = "Для этого правила не удаётся сформировать будущие даты.";
94
- "priority.low" = "Низкий";
95
- "priority.medium" = "Средний";
96
- "priority.high" = "Высокий";
97
- "status.open" = "Открыта";
98
- "status.done" = "Выполнена";
99
- "weekday.mon" = "Понедельник";
100
- "weekday.tue" = "Вторник";
101
- "weekday.wed" = "Среда";
102
- "weekday.thu" = "Четверг";
103
- "weekday.fri" = "Пятница";
104
- "weekday.sat" = "Суббота";
105
- "weekday.sun" = "Воскресенье";
106
- "validation.min_length" = "Минимум %d символа(ов)";
107
- "validation.min_value" = "Значение должно быть не меньше %d";
108
- "validation.max_value" = "Значение должно быть не больше %d";
109
- "validation.rule_name_min_length" = "Название правила должно содержать минимум %d символа(ов)";
110
- "validation.rule_name_reserved" = "Название по умолчанию зарезервировано. Укажите более точную метку.";
111
- "validation.rule_name_taken" = "Правило с таким названием уже существует.";
112
- "validation.match_field" = "Поля не совпадают";
113
- "validation.end_date_after_start" = "Дата окончания должна быть не раньше даты начала.";
114
- "validation.time_format" = "Используйте 24-часовой формат, например 09:00";
115
- "validation.month_day_max" = "Выберите день от 1 до 28";
116
- "validation.fix_errors" = "Исправьте выделенные поля перед сохранением.";
117
- "validation.required" = "Это поле обязательно.";
118
- "common.cancel" = "Отмена";
119
- "common.delete" = "Удалить";
@@ -1,50 +0,0 @@
1
- import SwiftUI
2
-
3
- @main
4
- struct TodoOrbitApp: App {
5
- @StateObject private var model = AppModel()
6
-
7
- var body: some Scene {
8
- WindowGroup {
9
- RootView(model: model)
10
- .preferredColorScheme(model.preferences.theme.colorScheme)
11
- .environment(\.locale, model.locale)
12
- .overlay(alignment: .bottom) {
13
- if let toast = model.toast {
14
- ToastOverlay(toast: toast)
15
- .transition(.move(edge: .bottom).combined(with: .opacity))
16
- }
17
- }
18
- }
19
- }
20
- }
21
-
22
- private struct RootView: View {
23
- @ObservedObject var model: AppModel
24
-
25
- var body: some View {
26
- TabView {
27
- NavigationStack {
28
- TasksHomeView(model: model)
29
- }
30
- .tabItem {
31
- Label(model.string("nav.tasks"), systemImage: "checklist")
32
- }
33
-
34
- NavigationStack {
35
- AnalyticsView(model: model)
36
- }
37
- .tabItem {
38
- Label(model.string("nav.analytics"), systemImage: "chart.line.uptrend.xyaxis")
39
- }
40
-
41
- NavigationStack {
42
- SettingsView(model: model)
43
- }
44
- .tabItem {
45
- Label(model.string("nav.settings"), systemImage: "gear")
46
- }
47
- }
48
- .tint(.teal)
49
- }
50
- }
@@ -1,204 +0,0 @@
1
- import SwiftUI
2
-
3
- struct CutCornerShape: Shape {
4
- var cut: CGFloat = 18
5
-
6
- func path(in rect: CGRect) -> Path {
7
- var path = Path()
8
- path.move(to: CGPoint(x: cut, y: rect.minY))
9
- path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
10
- path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cut))
11
- path.addLine(to: CGPoint(x: rect.maxX - cut, y: rect.maxY))
12
- path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
13
- path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cut))
14
- path.closeSubpath()
15
- return path
16
- }
17
- }
18
-
19
- struct OrbitSurfaceModifier: ViewModifier {
20
- let cut: CGFloat
21
- let fill: Color
22
- let stroke: Color
23
- let lineWidth: CGFloat
24
- let contentPadding: CGFloat
25
-
26
- func body(content: Content) -> some View {
27
- content
28
- .padding(contentPadding)
29
- .background(
30
- CutCornerShape(cut: cut)
31
- .fill(fill)
32
- )
33
- .overlay(
34
- CutCornerShape(cut: cut)
35
- .stroke(stroke, lineWidth: lineWidth)
36
- )
37
- }
38
- }
39
-
40
- extension View {
41
- func orbitSurface(
42
- cut: CGFloat = 16,
43
- fill: Color = Color(uiColor: .secondarySystemBackground),
44
- stroke: Color = Color.teal.opacity(0.24),
45
- lineWidth: CGFloat = 1,
46
- contentPadding: CGFloat = 20
47
- ) -> some View {
48
- modifier(
49
- OrbitSurfaceModifier(
50
- cut: cut,
51
- fill: fill,
52
- stroke: stroke,
53
- lineWidth: lineWidth,
54
- contentPadding: contentPadding
55
- )
56
- )
57
- }
58
-
59
- func orbitCard(
60
- fill: Color = Color(uiColor: .secondarySystemBackground),
61
- stroke: Color = Color.teal.opacity(0.24),
62
- lineWidth: CGFloat = 1
63
- ) -> some View {
64
- orbitSurface(fill: fill, stroke: stroke, lineWidth: lineWidth)
65
- }
66
-
67
- func orbitInputShell(
68
- cut: CGFloat = 14,
69
- fill: Color = Color(uiColor: .secondarySystemBackground),
70
- stroke: Color = Color.teal.opacity(0.22),
71
- lineWidth: CGFloat = 1.5,
72
- contentPadding: CGFloat = 14
73
- ) -> some View {
74
- orbitSurface(
75
- cut: cut,
76
- fill: fill,
77
- stroke: stroke,
78
- lineWidth: lineWidth,
79
- contentPadding: contentPadding
80
- )
81
- }
82
- }
83
-
84
- struct OrbitPrimaryButtonStyle: ButtonStyle {
85
- func makeBody(configuration: Configuration) -> some View {
86
- configuration.label
87
- .font(.headline.weight(.semibold))
88
- .foregroundStyle(.white)
89
- .padding(.horizontal, 18)
90
- .padding(.vertical, 14)
91
- .background(
92
- CutCornerShape(cut: 14)
93
- .fill(
94
- LinearGradient(
95
- colors: [Color.teal, Color(red: 0.07, green: 0.52, blue: 0.48)],
96
- startPoint: .topLeading,
97
- endPoint: .bottomTrailing
98
- )
99
- )
100
- )
101
- .scaleEffect(configuration.isPressed ? 0.98 : 1)
102
- }
103
- }
104
-
105
- struct OrbitGhostButtonStyle: ButtonStyle {
106
- func makeBody(configuration: Configuration) -> some View {
107
- configuration.label
108
- .font(.headline.weight(.semibold))
109
- .foregroundStyle(Color.primary)
110
- .padding(.horizontal, 16)
111
- .padding(.vertical, 12)
112
- .background(
113
- CutCornerShape(cut: 14)
114
- .fill(Color(uiColor: .secondarySystemBackground))
115
- )
116
- .overlay(
117
- CutCornerShape(cut: 14)
118
- .stroke(Color.teal.opacity(configuration.isPressed ? 0.34 : 0.18), lineWidth: 1.5)
119
- )
120
- .scaleEffect(configuration.isPressed ? 0.99 : 1)
121
- }
122
- }
123
-
124
- struct OrbitChipButtonStyle: ButtonStyle {
125
- let selected: Bool
126
-
127
- func makeBody(configuration: Configuration) -> some View {
128
- configuration.label
129
- .font(.subheadline.weight(.semibold))
130
- .foregroundStyle(selected ? Color.teal : Color.primary)
131
- .padding(.horizontal, 16)
132
- .padding(.vertical, 10)
133
- .background(
134
- CutCornerShape(cut: 12)
135
- .fill(selected ? Color.teal.opacity(0.12) : Color(uiColor: .secondarySystemBackground))
136
- )
137
- .overlay(
138
- CutCornerShape(cut: 12)
139
- .stroke(
140
- selected ? Color.teal.opacity(0.34) : Color(uiColor: .separator).opacity(0.45),
141
- lineWidth: selected ? 1.5 : 1
142
- )
143
- )
144
- .scaleEffect(configuration.isPressed ? 0.98 : 1)
145
- }
146
- }
147
-
148
- struct OrbitFloatingActionButtonStyle: ButtonStyle {
149
- func makeBody(configuration: Configuration) -> some View {
150
- configuration.label
151
- .font(.headline.weight(.semibold))
152
- .foregroundStyle(.white)
153
- .padding(.horizontal, 18)
154
- .padding(.vertical, 14)
155
- .background(
156
- CutCornerShape(cut: 16)
157
- .fill(
158
- LinearGradient(
159
- colors: [Color.teal, Color(red: 0.07, green: 0.52, blue: 0.48)],
160
- startPoint: .topLeading,
161
- endPoint: .bottomTrailing
162
- )
163
- )
164
- )
165
- .shadow(color: .black.opacity(configuration.isPressed ? 0.12 : 0.18), radius: 18, y: 8)
166
- .scaleEffect(configuration.isPressed ? 0.98 : 1)
167
- }
168
- }
169
-
170
- struct PriorityDot: View {
171
- let priority: TaskPriority
172
-
173
- var body: some View {
174
- Circle()
175
- .fill(priority.tint)
176
- .frame(width: 12, height: 12)
177
- }
178
- }
179
-
180
- struct ToastOverlay: View {
181
- let toast: ToastMessage
182
-
183
- var tint: Color {
184
- switch toast.level {
185
- case .success: .green
186
- case .warning: .orange
187
- case .error: .red
188
- }
189
- }
190
-
191
- var body: some View {
192
- Text(toast.text)
193
- .font(.subheadline.weight(.semibold))
194
- .foregroundStyle(.white)
195
- .padding(.horizontal, 18)
196
- .padding(.vertical, 14)
197
- .background(
198
- CutCornerShape(cut: 14)
199
- .fill(tint)
200
- )
201
- .shadow(color: .black.opacity(0.18), radius: 22, y: 10)
202
- .padding(.bottom, 18)
203
- }
204
- }
@@ -1,126 +0,0 @@
1
- import SwiftUI
2
-
3
- struct SchedulePreviewResult {
4
- enum State {
5
- case invalid
6
- case empty
7
- case ready
8
- }
9
-
10
- let state: State
11
- let dates: [Date]
12
- }
13
-
14
- struct SchedulePreviewView: View {
15
- let model: AppModel
16
- let title: String
17
- let result: SchedulePreviewResult
18
-
19
- var body: some View {
20
- VStack(alignment: .leading, spacing: 14) {
21
- Text("x_schedule_preview.detail")
22
- .font(.caption.weight(.bold))
23
- .foregroundStyle(.secondary)
24
- .textCase(.uppercase)
25
- Text(title)
26
- .font(.title3.weight(.semibold))
27
-
28
- switch result.state {
29
- case .invalid:
30
- Text(model.string("recurring_preview.invalid"))
31
- .font(.footnote.weight(.medium))
32
- .foregroundStyle(.red)
33
- .padding(14)
34
- .frame(maxWidth: .infinity, alignment: .leading)
35
- .background(Color.red.opacity(0.08), in: CutCornerShape(cut: 12))
36
- case .empty:
37
- Text(model.string("recurring_preview.empty"))
38
- .foregroundStyle(.secondary)
39
- case .ready:
40
- VStack(spacing: 10) {
41
- ForEach(Array(result.dates.enumerated()), id: \.offset) { index, date in
42
- HStack {
43
- Text(index == 0 ? "Next" : "+\(index)")
44
- .font(.caption.weight(.bold))
45
- .foregroundStyle(index == 0 ? .teal : .secondary)
46
- Spacer()
47
- Text(model.formatDate(date))
48
- .font(.subheadline.weight(.semibold))
49
- }
50
- .padding(14)
51
- .background(
52
- CutCornerShape(cut: 12)
53
- .fill(index == 0 ? Color.teal.opacity(0.08) : Color(uiColor: .secondarySystemBackground))
54
- )
55
- }
56
- }
57
- }
58
- }
59
- .orbitCard(fill: Color(uiColor: .systemBackground), stroke: Color.orange.opacity(0.22))
60
- }
61
-
62
- static func compute(from draft: RecurringRuleDraft) -> SchedulePreviewResult {
63
- guard let cadence = draft.cadence, let interval = Int(draft.interval), interval >= 1 else {
64
- return .init(state: .invalid, dates: [])
65
- }
66
-
67
- if draft.hasEndDate, draft.endDate < draft.startDate {
68
- return .init(state: .invalid, dates: [])
69
- }
70
-
71
- if cadence == .weekly, draft.weekday == nil {
72
- return .init(state: .invalid, dates: [])
73
- }
74
-
75
- if cadence == .monthly {
76
- guard let day = Int(draft.monthDay), (1...28).contains(day) else {
77
- return .init(state: .invalid, dates: [])
78
- }
79
- }
80
-
81
- let calendar = Calendar.current
82
- var dates: [Date] = []
83
- var cursor = draft.startDate
84
- let endDate = draft.hasEndDate ? draft.endDate : nil
85
-
86
- for _ in 0..<4 {
87
- let next: Date?
88
- switch cadence {
89
- case .daily:
90
- next = cursor
91
- cursor = calendar.date(byAdding: .day, value: interval, to: cursor) ?? cursor
92
- case .weekly:
93
- next = nextWeeklyDate(startingAt: cursor, weekday: draft.weekday!, interval: interval)
94
- cursor = calendar.date(byAdding: .day, value: interval * 7, to: next ?? cursor) ?? cursor
95
- case .monthly:
96
- next = nextMonthlyDate(startingAt: cursor, day: Int(draft.monthDay)!, interval: interval)
97
- cursor = calendar.date(byAdding: .month, value: interval, to: next ?? cursor) ?? cursor
98
- }
99
-
100
- guard let next else { break }
101
- if let endDate, next > endDate { break }
102
- dates.append(next)
103
- }
104
-
105
- return dates.isEmpty ? .init(state: .empty, dates: []) : .init(state: .ready, dates: dates)
106
- }
107
-
108
- private static func nextWeeklyDate(startingAt start: Date, weekday: Weekday, interval: Int) -> Date? {
109
- let calendar = Calendar.current
110
- var next = start
111
- while calendar.component(.weekday, from: next) != weekday.calendarIndex {
112
- guard let candidate = calendar.date(byAdding: .day, value: 1, to: next) else { return nil }
113
- next = candidate
114
- }
115
- return next
116
- }
117
-
118
- private static func nextMonthlyDate(startingAt start: Date, day: Int, interval: Int) -> Date? {
119
- let calendar = Calendar.current
120
- var components = calendar.dateComponents([.year, .month], from: start)
121
- components.day = day
122
- guard let initial = calendar.date(from: components) else { return nil }
123
- if initial >= start { return initial }
124
- return calendar.date(byAdding: .month, value: interval, to: initial)
125
- }
126
- }