@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.
- package/.claude/skills/ui-tester/SKILL.md +59 -0
- package/.cursor/skills/android-customizer/SKILL.md +50 -16
- package/.cursor/skills/appconfig-customization/SKILL.md +15 -0
- package/.cursor/skills/customization-planner/SKILL.md +17 -1
- package/.cursor/skills/ios-customizer/SKILL.md +54 -13
- package/.cursor/skills/mock-data-update/SKILL.md +11 -3
- package/.cursor/skills/ui-tester/SKILL.md +59 -0
- package/dist/cli.d.ts +18 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +134 -14
- package/dist/cli.js.map +1 -1
- package/dist/github.d.ts +5 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +27 -0
- package/dist/github.js.map +1 -1
- package/dist/index.js +218 -24
- package/dist/index.js.map +1 -1
- package/dist/preview-generator.d.ts +35 -0
- package/dist/preview-generator.d.ts.map +1 -0
- package/dist/preview-generator.js +546 -0
- package/dist/preview-generator.js.map +1 -0
- package/package.json +4 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +5 -3
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/bookings/BookingsScreen.kt +7 -5
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/DiscoveryViewModel.kt +3 -2
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/profile/ProfileScreen.kt +4 -2
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/AppConfig.kt +9 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/data/DataRepository.kt +23 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/data/DataSourceResolver.kt +23 -0
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/calls/CallsScreen.kt +4 -3
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/contacts/ContactsScreen.kt +3 -2
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ChatScreen.kt +11 -10
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/conversations/ConversationsScreen.kt +5 -5
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/navigation/Navigation.kt +4 -2
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/features/settings/SettingsScreen.kt +6 -4
- package/templates/android/ChatTemplate/app/src/main/kotlin/com/appship/chat/mock/MockDataProvider.kt +7 -7
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/data/DataRepository.kt +46 -0
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/data/DataSourceResolver.kt +23 -0
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/alerts/AlertsScreen.kt +4 -3
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/dashboard/DashboardScreen.kt +3 -2
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/datasources/DataSourcesScreen.kt +3 -2
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/features/reports/ReportsScreen.kt +3 -2
- package/templates/android/DashTemplate/app/src/main/kotlin/com/appship/dash/mock/MockDataProvider.kt +16 -15
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/AppConfig.kt +9 -0
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/calendar/CalendarScreen.kt +5 -5
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/chores/ChoreBoardScreen.kt +3 -3
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/familyhub/FamilyHubScreen.kt +4 -4
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/gallery/GalleryScreen.kt +3 -3
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/members/MembersListScreen.kt +5 -5
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/rewards/RewardsScreen.kt +5 -5
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/features/settings/SettingsScreen.kt +3 -3
- package/templates/android/FamilyTemplate/app/src/main/java/com/appship/family/mock/MockDataProvider.kt +29 -32
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/AppConfig.kt +9 -0
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/data/DataRepository.kt +70 -0
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/data/DataSourceResolver.kt +23 -0
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/budgets/BudgetsScreen.kt +3 -2
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/goals/GoalsScreen.kt +5 -3
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/home/HomeScreen.kt +8 -7
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/settings/SettingsScreen.kt +0 -1
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/features/transactions/TransactionsScreen.kt +9 -4
- package/templates/android/FinanceTemplate/app/src/main/kotlin/com/appship/finance/mock/MockDataProvider.kt +103 -11
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/data/DataRepository.kt +19 -0
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/leaderboard/LeaderboardScreen.kt +2 -1
- package/templates/android/GameTemplate/app/src/main/kotlin/com/appship/game/features/results/ResultsScreen.kt +3 -2
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/data/DataRepository.kt +46 -0
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/data/DataSourceResolver.kt +23 -0
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/dashboard/DashboardScreen.kt +3 -2
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/profile/ProfileScreen.kt +3 -2
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/features/workouts/WorkoutListScreen.kt +3 -2
- package/templates/android/HealthTemplate/app/src/main/kotlin/com/appship/health/mock/MockDataProvider.kt +13 -13
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/AppConfig.kt +9 -0
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/data/DataRepository.kt +48 -0
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/data/DataSourceResolver.kt +23 -0
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/courses/CourseListScreen.kt +4 -3
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/leaderboard/LeaderboardScreen.kt +3 -2
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/profile/ProfileScreen.kt +3 -2
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/features/progress/ProgressDashboardScreen.kt +3 -2
- package/templates/android/LearnTemplate/app/src/main/kotlin/com/appship/learn/mock/MockDataProvider.kt +41 -11
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +11 -6
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/di/AppModule.kt +11 -2
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/favorites/FavoritesViewModel.kt +4 -4
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/map/MapViewModel.kt +4 -4
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/place/PlaceDetailViewModel.kt +5 -5
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/place/PlaceListViewModel.kt +4 -4
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/profile/ProfileViewModel.kt +5 -5
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/route/RouteViewModel.kt +3 -3
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/search/SearchViewModel.kt +3 -3
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/features/settings/SettingsViewModel.kt +3 -3
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/mock/MockDataProvider.kt +54 -41
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/AppConfig.kt +9 -0
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/browse/BrowseScreen.kt +2 -1
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/detail/DetailScreen.kt +3 -1
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/features/home/HomeScreen.kt +3 -2
- package/templates/android/MediaTemplate/app/src/main/kotlin/com/appship/media/mock/MockDataProvider.kt +63 -132
- package/templates/android/ReferenceTemplate/app/src/main/kotlin/com/appship/reference/AppConfig.kt +9 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/home/HomeViewModel.kt +2 -1
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/features/products/ProductViewModels.kt +12 -7
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt +3 -3
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +0 -1
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/createpost/CreatePostScreen.kt +3 -2
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/CommentsScreen.kt +4 -3
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/FeedScreen.kt +3 -2
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/messages/MessagesScreen.kt +9 -8
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/notifications/NotificationsScreen.kt +3 -2
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/profile/ProfileScreen.kt +5 -4
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +3 -2
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/AppConfig.kt +9 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/data/DataRepository.kt +38 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/data/DataSourceResolver.kt +23 -0
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/calendar/CalendarScreen.kt +0 -1
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/inbox/InboxScreen.kt +4 -2
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/kanban/KanbanBoardScreen.kt +5 -3
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/projects/ProjectDetailScreen.kt +9 -5
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/projects/ProjectListScreen.kt +6 -4
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/search/SearchScreen.kt +1 -1
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/features/tasks/TaskDetailScreen.kt +0 -1
- package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/mock/MockDataProvider.kt +13 -12
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/AppConfig.kt +9 -0
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/data/DataRepository.kt +34 -0
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/data/DataSourceResolver.kt +23 -0
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/achievements/AchievementsViewModel.kt +4 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/dashboard/TodayViewModel.kt +4 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/habits/HabitDetailViewModel.kt +4 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/features/habits/HabitsListViewModel.kt +4 -2
- package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/mock/MockDataProvider.kt +13 -11
- package/templates/ios/ChatTemplate/ChatTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/ChatTemplate/ChatTemplate/Data/DataRepository.swift +43 -0
- package/templates/ios/ChatTemplate/ChatTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/DashTemplate/DashTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/DashTemplate/DashTemplate/Data/DataRepository.swift +53 -0
- package/templates/ios/DashTemplate/DashTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/FamilyTemplate/FamilyTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/FamilyTemplate/FamilyTemplate/App/FamilyTemplateApp.swift +4 -2
- package/templates/ios/FamilyTemplate/FamilyTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/FinanceTemplate/FinanceTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Data/DataRepository.swift +75 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Budgets/BudgetsView.swift +3 -1
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Goals/GoalsView.swift +3 -1
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Home/HomeView.swift +13 -6
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Settings/SettingsView.swift +6 -2
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Transactions/AddTransactionView.swift +4 -2
- package/templates/ios/FinanceTemplate/FinanceTemplate/Features/Transactions/TransactionsView.swift +3 -1
- package/templates/ios/FinanceTemplate/FinanceTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/GameTemplate/GameTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/GameTemplate/GameTemplate/Data/DataRepository.swift +30 -0
- package/templates/ios/GameTemplate/GameTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/GameTemplate/GameTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/HealthTemplate/HealthTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/HealthTemplate/HealthTemplate/Data/DataRepository.swift +60 -0
- package/templates/ios/HealthTemplate/HealthTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/LearnTemplate/LearnTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Courses/CourseDetailView.swift +3 -2
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Courses/CourseListView.swift +3 -2
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Flashcards/FlashcardView.swift +2 -1
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Leaderboard/LeaderboardView.swift +2 -1
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Lessons/LessonPlayerView.swift +4 -2
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Profile/ProfileView.swift +4 -2
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Progress/ProgressDashboardView.swift +3 -2
- package/templates/ios/LearnTemplate/LearnTemplate/Features/Quiz/QuizView.swift +2 -1
- package/templates/ios/LearnTemplate/LearnTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/MediaTemplate/MediaTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/MediaTemplate/MediaTemplate/Data/DataRepository.swift +52 -0
- package/templates/ios/MediaTemplate/MediaTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/MediaTemplate/MediaTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Data/DataRepository.swift +28 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/ReferenceTemplate/ReferenceTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/TaskTemplate/TaskTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/TaskTemplate/TaskTemplate/Data/DataRepository.swift +38 -0
- package/templates/ios/TaskTemplate/TaskTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Calendar/CalendarView.swift +6 -2
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Inbox/InboxView.swift +2 -1
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Kanban/KanbanBoardView.swift +9 -3
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Projects/ProjectDetailView.swift +3 -1
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Projects/ProjectListView.swift +3 -1
- package/templates/ios/TaskTemplate/TaskTemplate/Features/Search/SearchView.swift +3 -1
- package/templates/ios/TaskTemplate/TaskTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/TrackTemplate/TrackTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/TrackTemplate/TrackTemplate/Data/DataRepository.swift +29 -0
- package/templates/ios/TrackTemplate/TrackTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/TrackTemplate/TrackTemplate/MockData/MockDataProvider.swift +1 -1
- 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
|
|
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 = "${
|
|
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(
|
|
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
|
|
25
|
-
val
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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
|
|
62
|
-
val
|
|
63
|
-
val
|
|
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 ${
|
|
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.
|
|
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
|
package/templates/android/TaskTemplate/app/src/main/kotlin/com/appship/task/mock/MockDataProvider.kt
CHANGED
|
@@ -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
|
}
|
package/templates/android/TrackTemplate/app/src/main/kotlin/com/appship/track/data/DataRepository.kt
ADDED
|
@@ -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.
|
|
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 =
|
|
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.
|
|
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(
|
|
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 =
|
|
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(
|
|
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:
|
|
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
|
+
}
|
|
@@ -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 =
|
|
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
|
|
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
|