@shaykec/app-agent 1.0.4 → 1.0.5

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 (184) hide show
  1. package/.claude/skills/ui-tester/SKILL.md +59 -0
  2. package/.cursor/skills/android-customizer/SKILL.md +50 -16
  3. package/.cursor/skills/appconfig-customization/SKILL.md +15 -0
  4. package/.cursor/skills/customization-planner/SKILL.md +17 -1
  5. package/.cursor/skills/ios-customizer/SKILL.md +54 -13
  6. package/.cursor/skills/mock-data-update/SKILL.md +11 -3
  7. package/.cursor/skills/ui-tester/SKILL.md +59 -0
  8. package/dist/cli.d.ts +18 -4
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +134 -14
  11. package/dist/cli.js.map +1 -1
  12. package/dist/github.d.ts +5 -0
  13. package/dist/github.d.ts.map +1 -1
  14. package/dist/github.js +27 -0
  15. package/dist/github.js.map +1 -1
  16. package/dist/index.js +218 -24
  17. package/dist/index.js.map +1 -1
  18. package/dist/preview-generator.d.ts +35 -0
  19. package/dist/preview-generator.d.ts.map +1 -0
  20. package/dist/preview-generator.js +546 -0
  21. package/dist/preview-generator.js.map +1 -0
  22. package/package.json +4 -1
  23. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +5 -3
  24. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/bookings/BookingsScreen.kt +7 -5
  25. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/DiscoveryViewModel.kt +3 -2
  26. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/profile/ProfileScreen.kt +4 -2
  27. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/AppConfig.kt +9 -0
  28. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/data/DataRepository.kt +23 -0
  29. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/data/DataSourceResolver.kt +23 -0
  30. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/calls/CallsScreen.kt +4 -3
  31. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/contacts/ContactsScreen.kt +3 -2
  32. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ChatScreen.kt +11 -10
  33. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +5 -5
  34. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/navigation/Navigation.kt +4 -2
  35. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/settings/SettingsScreen.kt +6 -4
  36. package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/mock/MockDataProvider.kt +7 -7
  37. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/data/DataRepository.kt +46 -0
  38. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/data/DataSourceResolver.kt +23 -0
  39. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/alerts/AlertsScreen.kt +4 -3
  40. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/dashboard/DashboardScreen.kt +3 -2
  41. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/datasources/DataSourcesScreen.kt +3 -2
  42. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/reports/ReportsScreen.kt +3 -2
  43. package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/mock/MockDataProvider.kt +16 -15
  44. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/AppConfig.kt +9 -0
  45. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/calendar/CalendarScreen.kt +5 -5
  46. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/chores/ChoreBoardScreen.kt +3 -3
  47. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/familyhub/FamilyHubScreen.kt +4 -4
  48. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/gallery/GalleryScreen.kt +3 -3
  49. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/members/MembersListScreen.kt +5 -5
  50. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/rewards/RewardsScreen.kt +5 -5
  51. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/settings/SettingsScreen.kt +3 -3
  52. package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/mock/MockDataProvider.kt +29 -32
  53. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/AppConfig.kt +9 -0
  54. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/data/DataRepository.kt +70 -0
  55. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/data/DataSourceResolver.kt +23 -0
  56. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/budgets/BudgetsScreen.kt +3 -2
  57. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/goals/GoalsScreen.kt +5 -3
  58. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/home/HomeScreen.kt +8 -7
  59. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/settings/SettingsScreen.kt +0 -1
  60. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/transactions/TransactionsScreen.kt +9 -4
  61. package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/mock/MockDataProvider.kt +103 -11
  62. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/data/DataRepository.kt +19 -0
  63. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/leaderboard/LeaderboardScreen.kt +2 -1
  64. package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/results/ResultsScreen.kt +3 -2
  65. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/data/DataRepository.kt +46 -0
  66. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/data/DataSourceResolver.kt +23 -0
  67. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/dashboard/DashboardScreen.kt +3 -2
  68. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/profile/ProfileScreen.kt +3 -2
  69. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/workouts/WorkoutListScreen.kt +3 -2
  70. package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/mock/MockDataProvider.kt +13 -13
  71. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/AppConfig.kt +9 -0
  72. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/data/DataRepository.kt +48 -0
  73. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/data/DataSourceResolver.kt +23 -0
  74. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/courses/CourseListScreen.kt +4 -3
  75. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/leaderboard/LeaderboardScreen.kt +3 -2
  76. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/profile/ProfileScreen.kt +3 -2
  77. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/progress/ProgressDashboardScreen.kt +3 -2
  78. package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/mock/MockDataProvider.kt +41 -11
  79. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +11 -6
  80. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/di/AppModule.kt +11 -2
  81. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/favorites/FavoritesViewModel.kt +4 -4
  82. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/map/MapViewModel.kt +4 -4
  83. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/place/PlaceDetailViewModel.kt +5 -5
  84. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/place/PlaceListViewModel.kt +4 -4
  85. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/profile/ProfileViewModel.kt +5 -5
  86. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/route/RouteViewModel.kt +3 -3
  87. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/search/SearchViewModel.kt +3 -3
  88. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/settings/SettingsViewModel.kt +3 -3
  89. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/mock/MockDataProvider.kt +54 -41
  90. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/AppConfig.kt +9 -0
  91. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/browse/BrowseScreen.kt +2 -1
  92. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/detail/DetailScreen.kt +3 -1
  93. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/home/HomeScreen.kt +3 -2
  94. package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/mock/MockDataProvider.kt +63 -132
  95. package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/AppConfig.kt +9 -0
  96. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/home/HomeViewModel.kt +2 -1
  97. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/products/ProductViewModels.kt +12 -7
  98. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt +3 -3
  99. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +0 -1
  100. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/createpost/CreatePostScreen.kt +3 -2
  101. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/CommentsScreen.kt +4 -3
  102. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/FeedScreen.kt +3 -2
  103. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/messages/MessagesScreen.kt +9 -8
  104. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/notifications/NotificationsScreen.kt +3 -2
  105. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/profile/ProfileScreen.kt +5 -4
  106. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +3 -2
  107. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/AppConfig.kt +9 -0
  108. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/data/DataRepository.kt +38 -0
  109. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/data/DataSourceResolver.kt +23 -0
  110. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/calendar/CalendarScreen.kt +0 -1
  111. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/inbox/InboxScreen.kt +4 -2
  112. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/kanban/KanbanBoardScreen.kt +5 -3
  113. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/projects/ProjectDetailScreen.kt +9 -5
  114. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/projects/ProjectListScreen.kt +6 -4
  115. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/search/SearchScreen.kt +1 -1
  116. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/tasks/TaskDetailScreen.kt +0 -1
  117. package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/mock/MockDataProvider.kt +13 -12
  118. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/AppConfig.kt +9 -0
  119. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/data/DataRepository.kt +34 -0
  120. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/data/DataSourceResolver.kt +23 -0
  121. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/achievements/AchievementsViewModel.kt +4 -2
  122. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/dashboard/TodayViewModel.kt +4 -2
  123. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/habits/HabitDetailViewModel.kt +4 -2
  124. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/habits/HabitsListViewModel.kt +4 -2
  125. package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/mock/MockDataProvider.kt +13 -11
  126. package/templates/ios/ChatTemplate/ChatTemplate/App/AppConfig.swift +9 -0
  127. package/templates/ios/ChatTemplate/ChatTemplate/Data/DataRepository.swift +43 -0
  128. package/templates/ios/ChatTemplate/ChatTemplate/Data/DataSourceResolver.swift +20 -0
  129. package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +9 -0
  130. package/templates/ios/DashTemplate/DashTemplate/Data/DataRepository.swift +53 -0
  131. package/templates/ios/DashTemplate/DashTemplate/MockData/MockDataProvider.swift +1 -1
  132. package/templates/ios/FamilyTemplate/FamilyTemplate/App/AppConfig.swift +9 -0
  133. package/templates/ios/FamilyTemplate/FamilyTemplate/App/FamilyTemplateApp.swift +4 -2
  134. package/templates/ios/FamilyTemplate/FamilyTemplate/MockData/MockDataProvider.swift +1 -1
  135. package/templates/ios/FinanceTemplate/FinanceTemplate/App/AppConfig.swift +9 -0
  136. package/templates/ios/FinanceTemplate/FinanceTemplate/Data/DataRepository.swift +75 -0
  137. package/templates/ios/FinanceTemplate/FinanceTemplate/Data/DataSourceResolver.swift +20 -0
  138. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Budgets/BudgetsView.swift +3 -1
  139. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Goals/GoalsView.swift +3 -1
  140. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Home/HomeView.swift +13 -6
  141. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Settings/SettingsView.swift +6 -2
  142. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Transactions/AddTransactionView.swift +4 -2
  143. package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Transactions/TransactionsView.swift +3 -1
  144. package/templates/ios/FinanceTemplate/FinanceTemplate/MockData/MockDataProvider.swift +1 -1
  145. package/templates/ios/GameTemplate/GameTemplate/App/AppConfig.swift +9 -0
  146. package/templates/ios/GameTemplate/GameTemplate/Data/DataRepository.swift +30 -0
  147. package/templates/ios/GameTemplate/GameTemplate/Data/DataSourceResolver.swift +20 -0
  148. package/templates/ios/GameTemplate/GameTemplate/MockData/MockDataProvider.swift +1 -1
  149. package/templates/ios/HealthTemplate/HealthTemplate/App/AppConfig.swift +9 -0
  150. package/templates/ios/HealthTemplate/HealthTemplate/Data/DataRepository.swift +60 -0
  151. package/templates/ios/HealthTemplate/HealthTemplate/MockData/MockDataProvider.swift +1 -1
  152. package/templates/ios/LearnTemplate/LearnTemplate/App/AppConfig.swift +9 -0
  153. package/templates/ios/LearnTemplate/LearnTemplate/Features/Courses/CourseDetailView.swift +3 -2
  154. package/templates/ios/LearnTemplate/LearnTemplate/Features/Courses/CourseListView.swift +3 -2
  155. package/templates/ios/LearnTemplate/LearnTemplate/Features/Flashcards/FlashcardView.swift +2 -1
  156. package/templates/ios/LearnTemplate/LearnTemplate/Features/Leaderboard/LeaderboardView.swift +2 -1
  157. package/templates/ios/LearnTemplate/LearnTemplate/Features/Lessons/LessonPlayerView.swift +4 -2
  158. package/templates/ios/LearnTemplate/LearnTemplate/Features/Profile/ProfileView.swift +4 -2
  159. package/templates/ios/LearnTemplate/LearnTemplate/Features/Progress/ProgressDashboardView.swift +3 -2
  160. package/templates/ios/LearnTemplate/LearnTemplate/Features/Quiz/QuizView.swift +2 -1
  161. package/templates/ios/LearnTemplate/LearnTemplate/MockData/MockDataProvider.swift +1 -1
  162. package/templates/ios/MediaTemplate/MediaTemplate/App/AppConfig.swift +9 -0
  163. package/templates/ios/MediaTemplate/MediaTemplate/Data/DataRepository.swift +52 -0
  164. package/templates/ios/MediaTemplate/MediaTemplate/Data/DataSourceResolver.swift +20 -0
  165. package/templates/ios/MediaTemplate/MediaTemplate/MockData/MockDataProvider.swift +1 -1
  166. package/templates/ios/ReferenceTemplate/ReferenceTemplate/App/AppConfig.swift +9 -0
  167. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Data/DataRepository.swift +28 -0
  168. package/templates/ios/ReferenceTemplate/ReferenceTemplate/Data/DataSourceResolver.swift +20 -0
  169. package/templates/ios/ReferenceTemplate/ReferenceTemplate/MockData/MockDataProvider.swift +1 -1
  170. package/templates/ios/TaskTemplate/TaskTemplate/App/AppConfig.swift +9 -0
  171. package/templates/ios/TaskTemplate/TaskTemplate/Data/DataRepository.swift +38 -0
  172. package/templates/ios/TaskTemplate/TaskTemplate/Data/DataSourceResolver.swift +20 -0
  173. package/templates/ios/TaskTemplate/TaskTemplate/Features/Calendar/CalendarView.swift +6 -2
  174. package/templates/ios/TaskTemplate/TaskTemplate/Features/Inbox/InboxView.swift +2 -1
  175. package/templates/ios/TaskTemplate/TaskTemplate/Features/Kanban/KanbanBoardView.swift +9 -3
  176. package/templates/ios/TaskTemplate/TaskTemplate/Features/Projects/ProjectDetailView.swift +3 -1
  177. package/templates/ios/TaskTemplate/TaskTemplate/Features/Projects/ProjectListView.swift +3 -1
  178. package/templates/ios/TaskTemplate/TaskTemplate/Features/Search/SearchView.swift +3 -1
  179. package/templates/ios/TaskTemplate/TaskTemplate/MockData/MockDataProvider.swift +1 -1
  180. package/templates/ios/TrackTemplate/TrackTemplate/App/AppConfig.swift +9 -0
  181. package/templates/ios/TrackTemplate/TrackTemplate/Data/DataRepository.swift +29 -0
  182. package/templates/ios/TrackTemplate/TrackTemplate/Data/DataSourceResolver.swift +20 -0
  183. package/templates/ios/TrackTemplate/TrackTemplate/MockData/MockDataProvider.swift +1 -1
  184. package/templates/ios/TrackTemplate/TrackTemplate/Services/HabitManager.swift +1 -1
@@ -43,7 +43,9 @@ fun KanbanColumn(
43
43
  column: com.appship.task.data.KanbanColumn,
44
44
  modifier: Modifier = Modifier
45
45
  ) {
46
- val tasks = MockDataProvider.tasks.value.filter { column.taskIds.contains(it.id) }
46
+ val repository = remember { DataSourceResolver.repository }
47
+ val tasks by repository.tasks.collectAsState()
48
+ val columnTasks = tasks.filter { column.taskIds.contains(it.id) }
47
49
 
48
50
  Column(modifier = modifier) {
49
51
  Card(modifier = Modifier.fillMaxWidth()) {
@@ -56,7 +58,7 @@ fun KanbanColumn(
56
58
  style = MaterialTheme.typography.titleMedium
57
59
  )
58
60
  Text(
59
- text = "${tasks.size} tasks",
61
+ text = "${columnTasks.size} tasks",
60
62
  style = MaterialTheme.typography.bodySmall
61
63
  )
62
64
  }
@@ -67,7 +69,7 @@ fun KanbanColumn(
67
69
  LazyColumn(
68
70
  verticalArrangement = Arrangement.spacedBy(8.dp)
69
71
  ) {
70
- itemsIndexed(tasks) { index, task ->
72
+ itemsIndexed(columnTasks) { index, task ->
71
73
  KanbanTaskCard(
72
74
  task = task,
73
75
  modifier = Modifier.testTag("kanban_task_${index}_card")
@@ -21,10 +21,14 @@ fun ProjectDetailScreen(
21
21
  onBack: () -> Unit = {},
22
22
  onTaskClick: (String) -> Unit = {}
23
23
  ) {
24
- val project = MockDataProvider.projects.value.firstOrNull { it.id == projectId }
25
- val tasks = MockDataProvider.tasks.value
26
- .filter { project?.tasks?.contains(it.id) == true }
27
- .sortedByDescending { !it.isCompleted }
24
+ val repository = remember { DataSourceResolver.repository }
25
+ val projects by repository.projects.collectAsState()
26
+ val tasks by repository.tasks.collectAsState()
27
+ val project = remember(projects) { projects.firstOrNull { it.id == projectId } }
28
+ val projectTasks = remember(project, tasks) {
29
+ tasks.filter { project?.tasks?.contains(it.id) == true }
30
+ .sortedByDescending { !it.isCompleted }
31
+ }
28
32
 
29
33
  Scaffold(
30
34
  topBar = {
@@ -45,7 +49,7 @@ fun ProjectDetailScreen(
45
49
  contentPadding = PaddingValues(16.dp),
46
50
  verticalArrangement = Arrangement.spacedBy(8.dp)
47
51
  ) {
48
- itemsIndexed(tasks) { index, task ->
52
+ itemsIndexed(projectTasks) { index, task ->
49
53
  TaskCard(
50
54
  task = task,
51
55
  onClick = { onTaskClick(task.id) },
@@ -58,9 +58,11 @@ fun ProjectCard(
58
58
  onClick: () -> Unit,
59
59
  modifier: Modifier = Modifier
60
60
  ) {
61
- val tasks = MockDataProvider.tasks.value.filter { project.tasks.contains(it.id) }
62
- val completedCount = tasks.count { it.isCompleted }
63
- val progress = if (tasks.isEmpty()) 0.0 else completedCount.toDouble() / tasks.size
61
+ val repository = remember { DataSourceResolver.repository }
62
+ val tasks by repository.tasks.collectAsState()
63
+ val projectTasks = tasks.filter { project.tasks.contains(it.id) }
64
+ val completedCount = projectTasks.count { it.isCompleted }
65
+ val progress = if (projectTasks.isEmpty()) 0.0 else completedCount.toDouble() / projectTasks.size
64
66
 
65
67
  Card(
66
68
  onClick = onClick,
@@ -90,7 +92,7 @@ fun ProjectCard(
90
92
  )
91
93
  }
92
94
  Text(
93
- text = "$completedCount of ${tasks.size} tasks",
95
+ text = "$completedCount of ${projectTasks.size} tasks",
94
96
  style = MaterialTheme.typography.bodySmall,
95
97
  color = MaterialTheme.colorScheme.onSurfaceVariant
96
98
  )
@@ -8,7 +8,7 @@ import androidx.compose.runtime.*
8
8
  import androidx.compose.ui.Alignment
9
9
  import androidx.compose.ui.Modifier
10
10
  import androidx.compose.ui.unit.dp
11
- import com.appship.task.mock.MockDataProvider
11
+ import com.appship.task.data.DataSourceResolver
12
12
 
13
13
  @OptIn(ExperimentalMaterial3Api::class)
14
14
  @Composable
@@ -9,7 +9,6 @@ import androidx.compose.ui.Modifier
9
9
  import androidx.compose.ui.platform.testTag
10
10
  import androidx.compose.ui.unit.dp
11
11
  import com.appship.task.data.TaskPriority
12
- import com.appship.task.mock.MockDataProvider
13
12
 
14
13
  @OptIn(ExperimentalMaterial3Api::class)
15
14
  @Composable
@@ -1,13 +1,14 @@
1
1
  package com.appship.task.mock
2
2
 
3
3
  import com.appship.task.data.*
4
+ import com.appship.task.data.DataRepository
4
5
  import kotlinx.coroutines.flow.MutableStateFlow
5
6
  import kotlinx.coroutines.flow.StateFlow
6
7
  import kotlinx.coroutines.flow.asStateFlow
7
8
  import java.time.LocalDate
8
9
  import java.time.LocalDateTime
9
10
 
10
- object MockDataProvider {
11
+ object MockDataProvider : DataRepository {
11
12
 
12
13
  private val _tasks = MutableStateFlow<List<Task>>(emptyList())
13
14
  val tasks: StateFlow<List<Task>> = _tasks.asStateFlow()
@@ -294,22 +295,22 @@ object MockDataProvider {
294
295
 
295
296
  // MARK: - CRUD Operations
296
297
 
297
- fun getTasks(): List<Task> = _tasks.value
298
+ override fun getTasks(): List<Task> = _tasks.value
298
299
 
299
- fun getTask(id: String): Task? = _tasks.value.firstOrNull { it.id == id }
300
+ override fun getTask(id: String): Task? = _tasks.value.firstOrNull { it.id == id }
300
301
 
301
- fun createTask(task: Task) {
302
+ override fun createTask(task: Task) {
302
303
  _tasks.value = _tasks.value + task
303
304
  if (task.projectId != null) {
304
305
  updateProjectTasks(projectId = task.projectId, taskId = task.id, add = true)
305
306
  }
306
307
  }
307
308
 
308
- fun updateTask(task: Task) {
309
+ override fun updateTask(task: Task) {
309
310
  _tasks.value = _tasks.value.map { if (it.id == task.id) task else it }
310
311
  }
311
312
 
312
- fun deleteTask(id: String) {
313
+ override fun deleteTask(id: String) {
313
314
  val task = _tasks.value.firstOrNull { it.id == id }
314
315
  if (task != null && task.projectId != null) {
315
316
  updateProjectTasks(projectId = task.projectId, taskId = id, add = false)
@@ -321,21 +322,21 @@ object MockDataProvider {
321
322
  }
322
323
  }
323
324
 
324
- fun getProjects(): List<Project> = _projects.value
325
+ override fun getProjects(): List<Project> = _projects.value
325
326
 
326
- fun getProject(id: String): Project? = _projects.value.firstOrNull { it.id == id }
327
+ override fun getProject(id: String): Project? = _projects.value.firstOrNull { it.id == id }
327
328
 
328
- fun getTasksByTag(tagId: String): List<Task> {
329
+ override fun getTasksByTag(tagId: String): List<Task> {
329
330
  return _tasks.value.filter { it.tags.contains(tagId) }
330
331
  }
331
332
 
332
- fun getTasksByDueDate(date: LocalDate): List<Task> {
333
+ override fun getTasksByDueDate(date: LocalDate): List<Task> {
333
334
  return _tasks.value.filter { it.dueDate == date }
334
335
  }
335
336
 
336
- fun getKanbanColumns(): List<KanbanColumn> = _kanbanColumns.value
337
+ override fun getKanbanColumns(): List<KanbanColumn> = _kanbanColumns.value
337
338
 
338
- fun moveTaskToColumn(taskId: String, columnId: String) {
339
+ override fun moveTaskToColumn(taskId: String, columnId: String) {
339
340
  // Remove from all columns
340
341
  _kanbanColumns.value = _kanbanColumns.value.map { column ->
341
342
  column.copy(taskIds = column.taskIds.filter { it != taskId })
@@ -71,4 +71,13 @@ object AppConfig {
71
71
  0xFFDDA0DD, 0xFF98D8C8, 0xFFF7DC6F, 0xFFBB8FCE, 0xFF85C1E9,
72
72
  0xFFF8B500, 0xFFFF7F50, 0xFF6B5B95, 0xFF88B04B, 0xFFF7CAC9
73
73
  ).map { Color(it) }
74
+
75
+ // CUSTOMIZE:DATASOURCE - Data source configuration
76
+ object DataSource {
77
+ enum class SourceType {
78
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
79
+ MOCK // In-memory seed data — for development/testing only
80
+ }
81
+ val active: SourceType = SourceType.MOCK
82
+ }
74
83
  }
@@ -0,0 +1,34 @@
1
+ package com.appship.track.data
2
+
3
+ import kotlinx.coroutines.flow.StateFlow
4
+ import java.time.LocalDate
5
+
6
+ /**
7
+ * Abstracts the data layer. Both MockDataProvider and LocalStorageProvider
8
+ * implement this, allowing the app to swap data sources via AppConfig.DataSource.active.
9
+ *
10
+ * Screens and ViewModels depend on this interface rather than on a concrete implementation.
11
+ * Use [DataSourceResolver.repository] to get the active provider.
12
+ */
13
+ interface DataRepository {
14
+ // Observable state
15
+ val habits: StateFlow<List<Habit>>
16
+ val achievements: StateFlow<List<Achievement>>
17
+
18
+ // Legacy accessors for backward compatibility
19
+ val sampleHabits: List<Habit>
20
+ val achievementsList: List<Achievement>
21
+
22
+ // CRUD Operations for Habits
23
+ fun createHabit(habit: Habit)
24
+ fun updateHabit(habitId: String, update: (Habit) -> Habit)
25
+ fun deleteHabit(habitId: String)
26
+ fun archiveHabit(habitId: String, archived: Boolean = true)
27
+ fun toggleHabitCompletion(habitId: String, date: LocalDate, completed: Boolean)
28
+
29
+ // CRUD Operations for Achievements
30
+ fun updateAchievementProgress(achievementId: String, progress: Int, isUnlocked: Boolean = false)
31
+
32
+ // Reset
33
+ fun reset()
34
+ }
@@ -0,0 +1,23 @@
1
+ package com.appship.track.data
2
+
3
+ import com.appship.track.AppConfig
4
+ import com.appship.track.mock.MockDataProvider
5
+
6
+ /**
7
+ * Resolves the active [DataRepository] implementation based on [AppConfig.DataSource.active].
8
+ * Screens use this to obtain the correct data source without knowing the concrete type.
9
+ *
10
+ * Usage:
11
+ * val repository = DataSourceResolver.repository
12
+ * val habits by repository.habits.collectAsState()
13
+ */
14
+ object DataSourceResolver {
15
+ val repository: DataRepository
16
+ get() = when (AppConfig.DataSource.active) {
17
+ AppConfig.DataSource.SourceType.LOCAL_STORAGE -> {
18
+ // TODO: Return LocalStorageProvider once implemented
19
+ MockDataProvider
20
+ }
21
+ AppConfig.DataSource.SourceType.MOCK -> MockDataProvider
22
+ }
23
+ }
@@ -3,7 +3,7 @@ package com.appship.track.features.achievements
3
3
  import androidx.lifecycle.ViewModel
4
4
  import androidx.lifecycle.viewModelScope
5
5
  import com.appship.track.data.Achievement
6
- import com.appship.track.mock.MockDataProvider
6
+ import com.appship.track.data.DataSourceResolver
7
7
  import dagger.hilt.android.lifecycle.HiltViewModel
8
8
  import kotlinx.coroutines.flow.MutableStateFlow
9
9
  import kotlinx.coroutines.flow.StateFlow
@@ -22,6 +22,8 @@ data class AchievementsUiState(
22
22
  @HiltViewModel
23
23
  class AchievementsViewModel @Inject constructor() : ViewModel() {
24
24
 
25
+ private val repository = DataSourceResolver.repository
26
+
25
27
  private val _uiState = MutableStateFlow(AchievementsUiState())
26
28
  val uiState: StateFlow<AchievementsUiState> = _uiState.asStateFlow()
27
29
 
@@ -31,7 +33,7 @@ class AchievementsViewModel @Inject constructor() : ViewModel() {
31
33
 
32
34
  private fun loadAchievements() {
33
35
  viewModelScope.launch {
34
- val achievements = MockDataProvider.achievements.value
36
+ val achievements = repository.achievements.value
35
37
  val unlocked = achievements.filter { it.isUnlocked }
36
38
  val locked = achievements.filter { !it.isUnlocked }
37
39
 
@@ -3,7 +3,7 @@ package com.appship.track.features.dashboard
3
3
  import androidx.lifecycle.ViewModel
4
4
  import androidx.lifecycle.viewModelScope
5
5
  import com.appship.track.data.*
6
- import com.appship.track.mock.MockDataProvider
6
+ import com.appship.track.data.DataSourceResolver
7
7
  import dagger.hilt.android.lifecycle.HiltViewModel
8
8
  import kotlinx.coroutines.flow.MutableStateFlow
9
9
  import kotlinx.coroutines.flow.StateFlow
@@ -27,6 +27,8 @@ data class TodayUiState(
27
27
  @HiltViewModel
28
28
  class TodayViewModel @Inject constructor() : ViewModel() {
29
29
 
30
+ private val repository = DataSourceResolver.repository
31
+
30
32
  private val _uiState = MutableStateFlow(TodayUiState())
31
33
  val uiState: StateFlow<TodayUiState> = _uiState.asStateFlow()
32
34
 
@@ -40,7 +42,7 @@ class TodayViewModel @Inject constructor() : ViewModel() {
40
42
  private fun loadData() {
41
43
  viewModelScope.launch {
42
44
  habits.clear()
43
- habits.addAll(MockDataProvider.sampleHabits)
45
+ habits.addAll(repository.sampleHabits)
44
46
  updateDailySummary()
45
47
  }
46
48
  }
@@ -2,8 +2,8 @@ package com.appship.track.features.habits
2
2
 
3
3
  import androidx.lifecycle.ViewModel
4
4
  import androidx.lifecycle.viewModelScope
5
+ import com.appship.track.data.DataSourceResolver
5
6
  import com.appship.track.data.Habit
6
- import com.appship.track.mock.MockDataProvider
7
7
  import dagger.hilt.android.lifecycle.HiltViewModel
8
8
  import kotlinx.coroutines.flow.MutableStateFlow
9
9
  import kotlinx.coroutines.flow.StateFlow
@@ -25,12 +25,14 @@ data class HabitDetailUiState(
25
25
  @HiltViewModel
26
26
  class HabitDetailViewModel @Inject constructor() : ViewModel() {
27
27
 
28
+ private val repository = DataSourceResolver.repository
29
+
28
30
  private val _uiState = MutableStateFlow(HabitDetailUiState())
29
31
  val uiState: StateFlow<HabitDetailUiState> = _uiState.asStateFlow()
30
32
 
31
33
  fun loadHabit(habitId: String) {
32
34
  viewModelScope.launch {
33
- val habit = MockDataProvider.sampleHabits.find { it.id == habitId }
35
+ val habit = repository.sampleHabits.find { it.id == habitId }
34
36
 
35
37
  if (habit != null) {
36
38
  // Generate random stats for demo
@@ -2,8 +2,8 @@ package com.appship.track.features.habits
2
2
 
3
3
  import androidx.lifecycle.ViewModel
4
4
  import androidx.lifecycle.viewModelScope
5
+ import com.appship.track.data.DataSourceResolver
5
6
  import com.appship.track.data.Habit
6
- import com.appship.track.mock.MockDataProvider
7
7
  import dagger.hilt.android.lifecycle.HiltViewModel
8
8
  import kotlinx.coroutines.flow.MutableStateFlow
9
9
  import kotlinx.coroutines.flow.StateFlow
@@ -22,6 +22,8 @@ data class HabitsListUiState(
22
22
  @HiltViewModel
23
23
  class HabitsListViewModel @Inject constructor() : ViewModel() {
24
24
 
25
+ private val repository = DataSourceResolver.repository
26
+
25
27
  private val _uiState = MutableStateFlow(HabitsListUiState())
26
28
  val uiState: StateFlow<HabitsListUiState> = _uiState.asStateFlow()
27
29
 
@@ -34,7 +36,7 @@ class HabitsListViewModel @Inject constructor() : ViewModel() {
34
36
  private fun loadHabits() {
35
37
  viewModelScope.launch {
36
38
  allHabits.clear()
37
- allHabits.addAll(MockDataProvider.sampleHabits)
39
+ allHabits.addAll(repository.sampleHabits)
38
40
 
39
41
  // Generate random streaks
40
42
  val streaks = allHabits.associate { it.id to (1..21).random() }
@@ -1,12 +1,14 @@
1
1
  package com.appship.track.mock
2
2
 
3
3
  import com.appship.track.data.*
4
+ import com.appship.track.data.DataRepository
4
5
  import kotlinx.coroutines.flow.MutableStateFlow
5
6
  import kotlinx.coroutines.flow.StateFlow
6
7
  import kotlinx.coroutines.flow.asStateFlow
8
+ import java.time.LocalDate
7
9
  import java.time.LocalDateTime
8
10
 
9
- object MockDataProvider {
11
+ object MockDataProvider : DataRepository {
10
12
 
11
13
  private val defaultHabits = listOf(
12
14
  Habit(
@@ -153,17 +155,17 @@ object MockDataProvider {
153
155
  )
154
156
 
155
157
  private val _habits = MutableStateFlow(defaultHabits)
156
- val habits: StateFlow<List<Habit>> = _habits.asStateFlow()
158
+ override val habits: StateFlow<List<Habit>> = _habits.asStateFlow()
157
159
 
158
160
  private val _achievements = MutableStateFlow(defaultAchievements)
159
- val achievements: StateFlow<List<Achievement>> = _achievements.asStateFlow()
161
+ override val achievements: StateFlow<List<Achievement>> = _achievements.asStateFlow()
160
162
 
161
163
  // Legacy accessors for backward compatibility
162
- val sampleHabits: List<Habit> get() = _habits.value
163
- val achievementsList: List<Achievement> get() = _achievements.value
164
+ override val sampleHabits: List<Habit> get() = _habits.value
165
+ override val achievementsList: List<Achievement> get() = _achievements.value
164
166
 
165
167
  // CRUD Operations for Habits
166
- fun createHabit(habit: Habit) {
168
+ override fun createHabit(habit: Habit) {
167
169
  _habits.value = _habits.value + habit
168
170
  SyncManager.incrementPending()
169
171
  }
@@ -173,23 +175,23 @@ object MockDataProvider {
173
175
  SyncManager.incrementPending()
174
176
  }
175
177
 
176
- fun deleteHabit(habitId: String) {
178
+ override fun deleteHabit(habitId: String) {
177
179
  _habits.value = _habits.value.filter { it.id != habitId }
178
180
  SyncManager.incrementPending()
179
181
  }
180
182
 
181
- fun archiveHabit(habitId: String, archived: Boolean = true) {
183
+ override fun archiveHabit(habitId: String, archived: Boolean = true) {
182
184
  updateHabit(habitId) { it.copy(isArchived = archived) }
183
185
  }
184
186
 
185
- fun toggleHabitCompletion(habitId: String, date: java.time.LocalDate, completed: Boolean) {
187
+ override fun toggleHabitCompletion(habitId: String, date: LocalDate, completed: Boolean) {
186
188
  // This is a placeholder - actual implementation would work with HabitEntry
187
189
  // For now, we'll just mark it as a pending sync operation
188
190
  SyncManager.incrementPending()
189
191
  }
190
192
 
191
193
  // CRUD Operations for Achievements
192
- fun updateAchievementProgress(achievementId: String, progress: Int, isUnlocked: Boolean = false) {
194
+ override fun updateAchievementProgress(achievementId: String, progress: Int, isUnlocked: Boolean = false) {
193
195
  _achievements.value = _achievements.value.map {
194
196
  if (it.id == achievementId) {
195
197
  it.copy(
@@ -202,7 +204,7 @@ object MockDataProvider {
202
204
  SyncManager.incrementPending()
203
205
  }
204
206
 
205
- fun reset() {
207
+ override fun reset() {
206
208
  _habits.value = defaultHabits
207
209
  _achievements.value = defaultAchievements
208
210
  SyncManager.resetPending()
@@ -87,4 +87,13 @@ struct AppConfig {
87
87
  ("Share", "Send photos, videos, and voice messages", "photo.on.rectangle")
88
88
  ]
89
89
  }
90
+
91
+ // CUSTOMIZE:DATASOURCE - Data source configuration
92
+ struct DataSource {
93
+ enum SourceType {
94
+ case localStorage // Core Data — real local database (DEFAULT)
95
+ case mock // In-memory seed data — for development/testing only
96
+ }
97
+ static let active: SourceType = .mock
98
+ }
90
99
  }
@@ -0,0 +1,43 @@
1
+ import Foundation
2
+
3
+ /// Protocol abstracting the data layer. Both MockDataProvider and LocalStorageProvider
4
+ /// conform to this, allowing the app to swap data sources via AppConfig.DataSource.active.
5
+ ///
6
+ /// ViewModels depend on this protocol (via `any DataRepository`) rather than on a concrete
7
+ /// implementation. Use `DataSourceResolver.repository` to get the active provider.
8
+ protocol DataRepository: AnyObject {
9
+ // MARK: - Observable State
10
+
11
+ var currentUser: User { get }
12
+ var users: [User] { get }
13
+ var conversations: [Conversation] { get }
14
+ var messages: [String: [Message]] { get }
15
+ var calls: [Call] { get }
16
+ var contacts: [Contact] { get }
17
+
18
+ // MARK: - CRUD Operations - Messages
19
+
20
+ func sendMessage(_ message: Message)
21
+
22
+ // MARK: - CRUD Operations - Conversations
23
+
24
+ func createConversation(_ conversation: Conversation)
25
+ func deleteConversation(_ conversation: Conversation)
26
+ func pinConversation(_ conversation: Conversation)
27
+ func unpinConversation(_ conversation: Conversation)
28
+ func muteConversation(_ conversation: Conversation)
29
+ func unmuteConversation(_ conversation: Conversation)
30
+ func markMessagesAsRead(conversationId: String)
31
+
32
+ // MARK: - CRUD Operations - Calls
33
+
34
+ func addCall(_ call: Call)
35
+
36
+ // MARK: - Helper Functions
37
+
38
+ func getMessages(for conversationId: String) -> [Message]
39
+
40
+ // MARK: - Reset
41
+
42
+ func reset()
43
+ }
@@ -0,0 +1,20 @@
1
+ import Foundation
2
+
3
+ /// Resolves the active DataRepository implementation based on AppConfig.DataSource.active.
4
+ /// ViewModels use this to obtain the correct data source without knowing the concrete type.
5
+ ///
6
+ /// Usage:
7
+ /// let repo = DataSourceResolver.repository
8
+ /// let conversations = repo.conversations
9
+ enum DataSourceResolver {
10
+ /// Returns the active DataRepository based on the current configuration.
11
+ static var repository: any DataRepository {
12
+ switch AppConfig.DataSource.active {
13
+ case .localStorage:
14
+ // TODO: Return LocalStorageProvider.shared once implemented
15
+ return MockDataProvider.shared
16
+ case .mock:
17
+ return MockDataProvider.shared
18
+ }
19
+ }
20
+ }
@@ -99,4 +99,13 @@ struct AppConfig {
99
99
  ("Smart Alerts", "Get notified when metrics exceed thresholds", "bell.badge.fill")
100
100
  ]
101
101
  }
102
+
103
+ // CUSTOMIZE:DATASOURCE - Data source configuration
104
+ struct DataSource {
105
+ enum SourceType {
106
+ case localStorage // Core Data — real local database (DEFAULT)
107
+ case mock // In-memory seed data — for development/testing only
108
+ }
109
+ static let active: SourceType = .mock
110
+ }
102
111
  }
@@ -0,0 +1,53 @@
1
+ import Foundation
2
+
3
+ /// Protocol abstracting the data layer. Both MockDataProvider and LocalStorageProvider
4
+ /// conform to this, allowing the app to swap data sources via AppConfig.DataSource.active.
5
+ ///
6
+ /// ViewModels depend on this protocol (via `any DataRepository`) rather than on a concrete
7
+ /// implementation. Use `DataSourceResolver.repository` to get the active provider.
8
+ protocol DataRepository: AnyObject {
9
+ // MARK: - Observable State
10
+
11
+ var widgets: [Widget] { get }
12
+ var metrics: [Metric] { get }
13
+ var alerts: [Alert] { get }
14
+ var reports: [Report] { get }
15
+ var dataSources: [DataSource] { get }
16
+
17
+ var unreadAlertsCount: Int { get }
18
+
19
+ // MARK: - Data Loading
20
+
21
+ func loadData()
22
+
23
+ // MARK: - CRUD Operations - Widgets
24
+
25
+ func addWidget(_ widget: Widget)
26
+ func updateWidget(_ widget: Widget)
27
+ func deleteWidget(_ widget: Widget)
28
+ func reorderWidgets(from source: IndexSet, to destination: Int)
29
+
30
+ // MARK: - CRUD Operations - Metrics
31
+
32
+ func addMetric(_ metric: Metric)
33
+ func updateMetric(_ metric: Metric)
34
+ func deleteMetric(_ metric: Metric)
35
+
36
+ // MARK: - CRUD Operations - Alerts
37
+
38
+ func addAlert(_ alert: Alert)
39
+ func markAlertAsRead(_ alert: Alert)
40
+ func deleteAlert(_ alert: Alert)
41
+ func dismissAlert(_ alert: Alert)
42
+
43
+ // MARK: - CRUD Operations - Reports
44
+
45
+ func addReport(_ report: Report)
46
+ func deleteReport(_ report: Report)
47
+
48
+ // MARK: - CRUD Operations - Data Sources
49
+
50
+ func addDataSource(_ dataSource: DataSource)
51
+ func updateDataSource(_ dataSource: DataSource)
52
+ func deleteDataSource(_ dataSource: DataSource)
53
+ }
@@ -1,7 +1,7 @@
1
1
  import SwiftUI
2
2
  import Combine
3
3
 
4
- class MockDataProvider: ObservableObject {
4
+ class MockDataProvider: ObservableObject, DataRepository {
5
5
  static let shared = MockDataProvider()
6
6
 
7
7
  // MARK: - Published Mutable State
@@ -78,6 +78,15 @@ struct AppConfig {
78
78
  ("Share Moments", "Capture and share family memories", "photo.fill")
79
79
  ]
80
80
  }
81
+
82
+ // CUSTOMIZE:DATASOURCE - Data source configuration
83
+ struct DataSource {
84
+ enum SourceType {
85
+ case localStorage // Core Data — real local database (DEFAULT)
86
+ case mock // In-memory seed data — for development/testing only
87
+ }
88
+ static let active: SourceType = .mock
89
+ }
81
90
  }
82
91
 
83
92
  // MARK: - App State
@@ -4,7 +4,9 @@ import SwiftUI
4
4
  @main
5
5
  struct FamilyTemplateApp: App {
6
6
  @StateObject private var appState = AppState()
7
- @StateObject private var dataProvider = MockDataProvider.shared
7
+ @StateObject private var dataProvider: MockDataProvider = {
8
+ DataSourceResolver.repository as! MockDataProvider
9
+ }()
8
10
 
9
11
  var body: some Scene {
10
12
  WindowGroup {
@@ -35,5 +37,5 @@ struct ContentView: View {
35
37
  #Preview {
36
38
  ContentView()
37
39
  .environmentObject(AppState())
38
- .environmentObject(MockDataProvider.shared)
40
+ .environmentObject(DataSourceResolver.repository as! MockDataProvider)
39
41
  }
@@ -4,7 +4,7 @@ import Combine
4
4
 
5
5
  // MARK: - Mock Data Provider
6
6
  // CUSTOMIZE:API - Replace mock data with real data source
7
- class MockDataProvider: ObservableObject {
7
+ class MockDataProvider: ObservableObject, DataRepository {
8
8
  static let shared = MockDataProvider()
9
9
 
10
10
  // MARK: - Published Properties
@@ -33,6 +33,15 @@ struct AppConfig {
33
33
  static let termsURL = "https://example.com/terms"
34
34
  }
35
35
 
36
+ // CUSTOMIZE:DATASOURCE - Data source configuration
37
+ struct DataSource {
38
+ enum SourceType {
39
+ case localStorage // Core Data — real local database (DEFAULT)
40
+ case mock // In-memory seed data — for development/testing only
41
+ }
42
+ static let active: SourceType = .mock
43
+ }
44
+
36
45
  // CUSTOMIZE:THEME
37
46
  struct Theme {
38
47
  static let primaryColor = Color(hex: "10B981") // Green