openuispec 0.2.9 → 0.2.11

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 (232) hide show
  1. package/README.md +3 -1
  2. package/cli/index.ts +80 -2
  3. package/cli/init.ts +23 -8
  4. package/docs/cli.md +42 -7
  5. package/drift/index.ts +41 -15
  6. package/mcp-server/index.ts +247 -117
  7. package/mcp-server/screenshot-android.ts +185 -44
  8. package/mcp-server/screenshot-ios.ts +242 -30
  9. package/mcp-server/screenshot.ts +96 -1
  10. package/package.json +5 -2
  11. package/prepare/index.ts +16 -0
  12. package/scripts/take-all-screenshots.ts +507 -0
  13. package/status/index.ts +2 -2
  14. package/examples/social-app/.mcp.json +0 -10
  15. package/examples/social-app/AGENTS.md +0 -124
  16. package/examples/social-app/CLAUDE.md +0 -124
  17. package/examples/social-app/backend/.gitkeep +0 -1
  18. package/examples/social-app/generated/android/social-app/app/.paparazzi-hashes.json +0 -3
  19. package/examples/social-app/generated/android/social-app/app/build.gradle.kts +0 -94
  20. package/examples/social-app/generated/android/social-app/app/src/main/AndroidManifest.xml +0 -26
  21. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/AppContainer.kt +0 -20
  22. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/MainActivity.kt +0 -35
  23. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/SocialAppApplication.kt +0 -13
  24. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/MockData.kt +0 -98
  25. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/AppPreferences.kt +0 -19
  26. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/DataStorePreferencesRepository.kt +0 -68
  27. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/data/preferences/PreferencesRepository.kt +0 -15
  28. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/model/Models.kt +0 -34
  29. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/MainShell.kt +0 -390
  30. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/Components.kt +0 -234
  31. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/components/ContractPrimitives.kt +0 -641
  32. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/navigation/RootComponent.kt +0 -113
  33. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ChatDetailScreen.kt +0 -212
  34. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/CreatePostScreen.kt +0 -113
  35. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/DiscoverScreen.kt +0 -137
  36. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/EditProfileScreen.kt +0 -180
  37. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/HomeFeedScreen.kt +0 -169
  38. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/MessagesInboxScreen.kt +0 -85
  39. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/NotificationsScreen.kt +0 -74
  40. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/PostDetailScreen.kt +0 -293
  41. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/ProfileSelfScreen.kt +0 -116
  42. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SearchResultsScreen.kt +0 -161
  43. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsScreen.kt +0 -164
  44. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/SettingsStore.kt +0 -95
  45. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/screens/UserProfileScreen.kt +0 -123
  46. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Color.kt +0 -33
  47. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Shape.kt +0 -41
  48. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Spacing.kt +0 -20
  49. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Theme.kt +0 -82
  50. package/examples/social-app/generated/android/social-app/app/src/main/java/com/social/app/ui/theme/Type.kt +0 -60
  51. package/examples/social-app/generated/android/social-app/app/src/main/res/drawable/ic_launcher_foreground.xml +0 -9
  52. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -5
  53. package/examples/social-app/generated/android/social-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -5
  54. package/examples/social-app/generated/android/social-app/app/src/main/res/values/strings.xml +0 -91
  55. package/examples/social-app/generated/android/social-app/app/src/main/res/values/themes.xml +0 -10
  56. package/examples/social-app/generated/android/social-app/app/src/main/res/values-ru/strings.xml +0 -79
  57. package/examples/social-app/generated/android/social-app/app/src/main/res/values-uz/strings.xml +0 -79
  58. package/examples/social-app/generated/android/social-app/app/src/main/xml/AndroidManifest.xml +0 -23
  59. package/examples/social-app/generated/android/social-app/app/src/test/kotlin/com/social/app/screenshots/HomeFeedScreenshotTest.kt +0 -34
  60. package/examples/social-app/generated/android/social-app/build.gradle.kts +0 -7
  61. package/examples/social-app/generated/android/social-app/gradle/libs.versions.toml +0 -50
  62. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.jar +0 -0
  63. package/examples/social-app/generated/android/social-app/gradle/wrapper/gradle-wrapper.properties +0 -8
  64. package/examples/social-app/generated/android/social-app/gradle.properties +0 -11
  65. package/examples/social-app/generated/android/social-app/gradlew +0 -248
  66. package/examples/social-app/generated/android/social-app/settings.gradle.kts +0 -27
  67. package/examples/social-app/generated/web/social-app/index.html +0 -12
  68. package/examples/social-app/generated/web/social-app/package-lock.json +0 -2517
  69. package/examples/social-app/generated/web/social-app/package.json +0 -27
  70. package/examples/social-app/generated/web/social-app/src/app/App.tsx +0 -58
  71. package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +0 -259
  72. package/examples/social-app/generated/web/social-app/src/components/cards.tsx +0 -317
  73. package/examples/social-app/generated/web/social-app/src/components/ui.tsx +0 -340
  74. package/examples/social-app/generated/web/social-app/src/flows/CreatePostFlow.tsx +0 -86
  75. package/examples/social-app/generated/web/social-app/src/i18n.tsx +0 -59
  76. package/examples/social-app/generated/web/social-app/src/lib/icons.tsx +0 -85
  77. package/examples/social-app/generated/web/social-app/src/lib/tokens.ts +0 -70
  78. package/examples/social-app/generated/web/social-app/src/lib/utils.ts +0 -97
  79. package/examples/social-app/generated/web/social-app/src/locales/en.json +0 -67
  80. package/examples/social-app/generated/web/social-app/src/locales/ru.json +0 -67
  81. package/examples/social-app/generated/web/social-app/src/locales/uz.json +0 -67
  82. package/examples/social-app/generated/web/social-app/src/main.tsx +0 -16
  83. package/examples/social-app/generated/web/social-app/src/screens/ChatDetailScreen.tsx +0 -90
  84. package/examples/social-app/generated/web/social-app/src/screens/DiscoverScreen.tsx +0 -86
  85. package/examples/social-app/generated/web/social-app/src/screens/EditProfileScreen.tsx +0 -57
  86. package/examples/social-app/generated/web/social-app/src/screens/HomeFeedScreen.tsx +0 -103
  87. package/examples/social-app/generated/web/social-app/src/screens/MessagesInboxScreen.tsx +0 -52
  88. package/examples/social-app/generated/web/social-app/src/screens/NotificationsScreen.tsx +0 -41
  89. package/examples/social-app/generated/web/social-app/src/screens/PostDetailScreen.tsx +0 -115
  90. package/examples/social-app/generated/web/social-app/src/screens/ProfileSelfScreen.tsx +0 -57
  91. package/examples/social-app/generated/web/social-app/src/screens/ProfileUserScreen.tsx +0 -76
  92. package/examples/social-app/generated/web/social-app/src/screens/SearchResultsScreen.tsx +0 -96
  93. package/examples/social-app/generated/web/social-app/src/screens/SettingsScreen.tsx +0 -79
  94. package/examples/social-app/generated/web/social-app/src/state/store.ts +0 -592
  95. package/examples/social-app/generated/web/social-app/src/styles.css +0 -125
  96. package/examples/social-app/generated/web/social-app/src/vite-env.d.ts +0 -1
  97. package/examples/social-app/generated/web/social-app/tsconfig.json +0 -22
  98. package/examples/social-app/generated/web/social-app/tsconfig.node.json +0 -13
  99. package/examples/social-app/generated/web/social-app/tsconfig.node.tsbuildinfo +0 -1
  100. package/examples/social-app/generated/web/social-app/tsconfig.tsbuildinfo +0 -1
  101. package/examples/social-app/generated/web/social-app/vite.config.d.ts +0 -2
  102. package/examples/social-app/generated/web/social-app/vite.config.js +0 -6
  103. package/examples/social-app/generated/web/social-app/vite.config.ts +0 -7
  104. package/examples/social-app/package.json +0 -13
  105. package/examples/social-app/take-web-screenshots.ts +0 -97
  106. package/examples/taskflow/.codex/config.toml +0 -4
  107. package/examples/taskflow/.mcp.json +0 -10
  108. package/examples/taskflow/AGENTS.md +0 -124
  109. package/examples/taskflow/CLAUDE.md +0 -124
  110. package/examples/taskflow/backend/.gitkeep +0 -1
  111. package/examples/taskflow/generated/android/TaskFlow/README.md +0 -43
  112. package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +0 -76
  113. package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +0 -1
  114. package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +0 -21
  115. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +0 -19
  116. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +0 -283
  117. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +0 -106
  118. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +0 -57
  119. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +0 -109
  120. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +0 -112
  121. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +0 -61
  122. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +0 -82
  123. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +0 -111
  124. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +0 -77
  125. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +0 -30
  126. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +0 -86
  127. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +0 -57
  128. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +0 -155
  129. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +0 -4
  130. package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +0 -5
  131. package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +0 -12
  132. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
  133. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +0 -7
  134. package/examples/taskflow/generated/android/TaskFlow/gradle.properties +0 -4
  135. package/examples/taskflow/generated/android/TaskFlow/gradlew +0 -18
  136. package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +0 -12
  137. package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +0 -18
  138. package/examples/taskflow/generated/ios/TaskFlow/README.md +0 -21
  139. package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +0 -115
  140. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +0 -24
  141. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +0 -150
  142. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +0 -220
  143. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +0 -122
  144. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +0 -21
  145. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +0 -201
  146. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +0 -48
  147. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +0 -59
  148. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +0 -63
  149. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +0 -85
  150. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +0 -219
  151. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +0 -320
  152. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +0 -41
  153. package/examples/taskflow/generated/ios/TaskFlow/project.yml +0 -26
  154. package/examples/taskflow/generated/web/TaskFlow/README.md +0 -19
  155. package/examples/taskflow/generated/web/TaskFlow/index.html +0 -12
  156. package/examples/taskflow/generated/web/TaskFlow/package-lock.json +0 -1908
  157. package/examples/taskflow/generated/web/TaskFlow/package.json +0 -24
  158. package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +0 -58
  159. package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +0 -55
  160. package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +0 -82
  161. package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +0 -191
  162. package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +0 -41
  163. package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +0 -131
  164. package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +0 -25
  165. package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +0 -39
  166. package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +0 -111
  167. package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +0 -13
  168. package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +0 -111
  169. package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +0 -82
  170. package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +0 -132
  171. package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +0 -105
  172. package/examples/taskflow/generated/web/TaskFlow/src/store.ts +0 -216
  173. package/examples/taskflow/generated/web/TaskFlow/src/styles.css +0 -617
  174. package/examples/taskflow/generated/web/TaskFlow/src/types.ts +0 -64
  175. package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +0 -78
  176. package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +0 -21
  177. package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +0 -6
  178. package/examples/todo-orbit/.codex/config.toml +0 -4
  179. package/examples/todo-orbit/.mcp.json +0 -10
  180. package/examples/todo-orbit/AGENTS.md +0 -124
  181. package/examples/todo-orbit/CLAUDE.md +0 -124
  182. package/examples/todo-orbit/backend/.gitkeep +0 -1
  183. package/examples/todo-orbit/generated/android/Todo Orbit/README.md +0 -14
  184. package/examples/todo-orbit/generated/android/Todo Orbit/app/build.gradle.kts +0 -58
  185. package/examples/todo-orbit/generated/android/Todo Orbit/app/proguard-rules.pro +0 -1
  186. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/AndroidManifest.xml +0 -20
  187. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/MainActivity.kt +0 -14
  188. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/TodoOrbitApp.kt +0 -345
  189. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/AppLogic.kt +0 -231
  190. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Models.kt +0 -169
  191. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/support/Strings.kt +0 -8
  192. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +0 -236
  193. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/AnalyticsScreen.kt +0 -193
  194. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/SettingsScreen.kt +0 -102
  195. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +0 -347
  196. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +0 -347
  197. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/theme/TodoOrbitTheme.kt +0 -59
  198. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +0 -149
  199. package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +0 -155
  200. package/examples/todo-orbit/generated/android/Todo Orbit/build.gradle.kts +0 -4
  201. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.jar +0 -0
  202. package/examples/todo-orbit/generated/android/Todo Orbit/gradle/wrapper/gradle-wrapper.properties +0 -7
  203. package/examples/todo-orbit/generated/android/Todo Orbit/gradle.properties +0 -4
  204. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew +0 -248
  205. package/examples/todo-orbit/generated/android/Todo Orbit/gradlew.bat +0 -93
  206. package/examples/todo-orbit/generated/android/Todo Orbit/settings.gradle.kts +0 -18
  207. package/examples/todo-orbit/generated/ios/Todo Orbit/.screenshot-uitest/Sources/ScreenshotUITest.swift +0 -36
  208. package/examples/todo-orbit/generated/ios/Todo Orbit/README.md +0 -29
  209. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +0 -119
  210. package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +0 -119
  211. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/App/TodoOrbitApp.swift +0 -50
  212. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/OrbitChrome.swift +0 -204
  213. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/SchedulePreviewView.swift +0 -126
  214. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Components/TrendChartView.swift +0 -70
  215. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +0 -126
  216. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +0 -61
  217. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Models/DomainModels.swift +0 -238
  218. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/AnalyticsView.swift +0 -94
  219. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +0 -76
  220. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +0 -364
  221. package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +0 -324
  222. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.pbxproj +0 -400
  223. package/examples/todo-orbit/generated/ios/Todo Orbit/TodoOrbit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  224. package/examples/todo-orbit/generated/ios/Todo Orbit/project.yml +0 -25
  225. package/examples/todo-orbit/generated/web/Todo Orbit/index.html +0 -16
  226. package/examples/todo-orbit/generated/web/Todo Orbit/package-lock.json +0 -1087
  227. package/examples/todo-orbit/generated/web/Todo Orbit/package.json +0 -24
  228. package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +0 -2167
  229. package/examples/todo-orbit/generated/web/Todo Orbit/src/main.tsx +0 -13
  230. package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +0 -926
  231. package/examples/todo-orbit/generated/web/Todo Orbit/tsconfig.json +0 -19
  232. 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
- }