@shaykec/app-agent 1.0.3 → 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/AppConfig.kt +9 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/data/DataSourceResolver.kt +23 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +6 -4
- 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/discovery/ProviderDetailScreen.kt +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/SearchScreen.kt +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/profile/ProfileScreen.kt +4 -2
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/mock/MockDataProvider.kt +12 -11
- 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/AppConfig.kt +9 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataRepository.kt +33 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +28 -0
- 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/AppConfig.kt +9 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataRepository.kt +62 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataSourceResolver.kt +23 -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 +31 -31
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/AppConfig.kt +9 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataRepository.kt +30 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataSourceResolver.kt +23 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/create/CreateScreen.kt +3 -2
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/detail/DetailScreen.kt +4 -3
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/explore/ExploreScreen.kt +7 -6
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/favorites/FavoritesScreen.kt +4 -3
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/home/HomeScreen.kt +5 -4
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/mock/MockDataProvider.kt +12 -11
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/AppConfig.kt +9 -0
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +37 -0
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataSourceResolver.kt +23 -0
- 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 +5 -4
- 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 +6 -5
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +4 -3
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/mock/MockDataProvider.kt +7 -6
- 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/BookTemplate/BookTemplate/App/AppConfig.swift +12 -2
- package/templates/ios/BookTemplate/BookTemplate/Data/DataRepository.swift +33 -0
- package/templates/ios/BookTemplate/BookTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/BookTemplate/BookTemplate/Features/Booking/BookingView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/DiscoveryView.swift +3 -3
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/MapExploreView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/ProviderDetailView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/SearchView.swift +2 -2
- package/templates/ios/BookTemplate/BookTemplate/MockData/MockDataProvider.swift +18 -1
- 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/MapTemplate/MapTemplate/App/AppConfig.swift +11 -9
- package/templates/ios/MapTemplate/MapTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/MapTemplate/MapTemplate/Features/Favorites/FavoritesView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/PlaceDetail/PlaceDetailView.swift +2 -2
- package/templates/ios/MapTemplate/MapTemplate/Features/Profile/ProfileView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/Route/RouteView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/Search/SearchView.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/ShopTemplate/ShopTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/ShopTemplate/ShopTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Home/HomeView.swift +9 -7
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Orders/OrdersView.swift +7 -3
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductDetailView.swift +4 -2
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductListView.swift +6 -3
- package/templates/ios/ShopTemplate/ShopTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/Skeleton/Skeleton/App/AppConfig.swift +9 -0
- package/templates/ios/Skeleton/Skeleton/Data/DataRepository.swift +30 -0
- package/templates/ios/Skeleton/Skeleton/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/Skeleton/Skeleton/Features/Explore/ExploreViewModel.swift +7 -6
- package/templates/ios/Skeleton/Skeleton/Features/Home/HomeViewModel.swift +8 -7
- package/templates/ios/Skeleton/Skeleton/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/App/AppConfig.swift +4 -3
- package/templates/ios/SocialTemplate/SocialTemplate/Data/DataRepository.swift +37 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Data/SyncManager.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/CreatePost/CreatePostView.swift +2 -2
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/CommentsView.swift +2 -2
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/FeedView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Messages/MessagesView.swift +4 -4
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Notifications/NotificationsView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Profile/ProfileView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Search/SearchView.swift +5 -4
- package/templates/ios/SocialTemplate/SocialTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Models/Models.swift +2 -2
- 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
|
@@ -253,15 +253,17 @@ class ProductDetailViewModel: ObservableObject {
|
|
|
253
253
|
@Published var isInWishlist = false
|
|
254
254
|
|
|
255
255
|
private let wishlistManager = WishlistManager()
|
|
256
|
+
private let repository: any DataRepository
|
|
256
257
|
|
|
257
|
-
init(product: Product) {
|
|
258
|
+
init(product: Product, repository: any DataRepository = DataSourceResolver.repository) {
|
|
258
259
|
self.product = product
|
|
260
|
+
self.repository = repository
|
|
259
261
|
self.isInWishlist = wishlistManager.isInWishlist(product)
|
|
260
262
|
loadReviews()
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
func loadReviews() {
|
|
264
|
-
reviews =
|
|
266
|
+
reviews = repository.reviews(for: product.id)
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
func toggleWishlist() {
|
|
@@ -222,8 +222,11 @@ class ProductListViewModel: ObservableObject {
|
|
|
222
222
|
return result
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
private let repository: any DataRepository
|
|
226
|
+
|
|
227
|
+
init(category: Category?, products: [Product]? = nil, repository: any DataRepository = DataSourceResolver.repository) {
|
|
226
228
|
self.category = category
|
|
229
|
+
self.repository = repository
|
|
227
230
|
if let products = products {
|
|
228
231
|
self.products = products
|
|
229
232
|
} else {
|
|
@@ -237,9 +240,9 @@ class ProductListViewModel: ObservableObject {
|
|
|
237
240
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
|
238
241
|
guard let self = self else { return }
|
|
239
242
|
if let category = self.category {
|
|
240
|
-
self.products =
|
|
243
|
+
self.products = self.repository.products(for: category)
|
|
241
244
|
} else {
|
|
242
|
-
self.products =
|
|
245
|
+
self.products = self.repository.products
|
|
243
246
|
}
|
|
244
247
|
self.isLoading = false
|
|
245
248
|
}
|
|
@@ -5,7 +5,7 @@ import Combine
|
|
|
5
5
|
// CUSTOMIZE:API - Replace mock data with real API calls
|
|
6
6
|
// Mutable observable singleton. Seed data lives in `default*` static properties.
|
|
7
7
|
// The agent customizes seed data; CRUD methods and infrastructure are generic.
|
|
8
|
-
class MockDataProvider: ObservableObject {
|
|
8
|
+
class MockDataProvider: ObservableObject, DataRepository {
|
|
9
9
|
static let shared = MockDataProvider()
|
|
10
10
|
|
|
11
11
|
// MARK: - Published Mutable State
|
|
@@ -83,6 +83,15 @@ struct AppConfig {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// CUSTOMIZE:DATASOURCE - Data source configuration
|
|
87
|
+
struct DataSource {
|
|
88
|
+
enum SourceType {
|
|
89
|
+
case localStorage // Core Data — real local database (DEFAULT)
|
|
90
|
+
case mock // In-memory seed data — for development/testing only
|
|
91
|
+
}
|
|
92
|
+
static let active: SourceType = .mock
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
// CUSTOMIZE:CATEGORIES - Domain categories for content
|
|
87
96
|
static let categories: [(name: String, icon: String)] = [
|
|
88
97
|
("Category 1", "folder.fill"),
|
|
@@ -0,0 +1,30 @@
|
|
|
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 items: [Item] { get }
|
|
12
|
+
var categories: [Category] { get }
|
|
13
|
+
var favoriteItems: [Item] { get }
|
|
14
|
+
|
|
15
|
+
// MARK: - Data Loading
|
|
16
|
+
|
|
17
|
+
func loadData()
|
|
18
|
+
|
|
19
|
+
// MARK: - CRUD Operations
|
|
20
|
+
|
|
21
|
+
func addItem(_ item: Item)
|
|
22
|
+
func updateItem(_ item: Item)
|
|
23
|
+
func deleteItem(_ item: Item)
|
|
24
|
+
func toggleFavorite(_ item: Item)
|
|
25
|
+
|
|
26
|
+
// MARK: - Queries
|
|
27
|
+
|
|
28
|
+
func searchItems(query: String) -> [Item]
|
|
29
|
+
func itemsByCategory(_ categoryName: String) -> [Item]
|
|
30
|
+
}
|
|
@@ -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 items = repo.items
|
|
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
|
+
}
|
|
@@ -6,21 +6,22 @@ class ExploreViewModel: ObservableObject {
|
|
|
6
6
|
@Published var categories: [Category] = []
|
|
7
7
|
@Published var searchResults: [Item] = []
|
|
8
8
|
|
|
9
|
-
private let
|
|
9
|
+
private let repository: any DataRepository
|
|
10
10
|
|
|
11
|
-
init() {
|
|
12
|
-
|
|
11
|
+
init(repository: any DataRepository = DataSourceResolver.repository) {
|
|
12
|
+
self.repository = repository
|
|
13
|
+
categories = repository.categories
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
func search(query: String) {
|
|
16
|
-
searchResults =
|
|
17
|
+
searchResults = repository.searchItems(query: query)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
func itemsForCategory(_ name: String) -> [Item] {
|
|
20
|
-
|
|
21
|
+
repository.itemsByCategory(name)
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
func toggleFavorite(_ item: Item) {
|
|
24
|
-
|
|
25
|
+
repository.toggleFavorite(item)
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -9,20 +9,21 @@ class HomeViewModel: ObservableObject {
|
|
|
9
9
|
@Published var filteredItems: [Item] = []
|
|
10
10
|
@Published var selectedCategory: Category?
|
|
11
11
|
|
|
12
|
-
private let
|
|
12
|
+
private let repository: any DataRepository
|
|
13
13
|
|
|
14
|
-
init() {
|
|
14
|
+
init(repository: any DataRepository = DataSourceResolver.repository) {
|
|
15
|
+
self.repository = repository
|
|
15
16
|
loadData()
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
func loadData() {
|
|
19
|
-
items =
|
|
20
|
-
categories =
|
|
20
|
+
items = repository.items
|
|
21
|
+
categories = repository.categories
|
|
21
22
|
filteredItems = items
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
func refresh() {
|
|
25
|
-
|
|
26
|
+
repository.loadData()
|
|
26
27
|
loadData()
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -30,7 +31,7 @@ class HomeViewModel: ObservableObject {
|
|
|
30
31
|
if query.isEmpty {
|
|
31
32
|
applyFilters()
|
|
32
33
|
} else {
|
|
33
|
-
let searched =
|
|
34
|
+
let searched = repository.searchItems(query: query)
|
|
34
35
|
if let cat = selectedCategory {
|
|
35
36
|
filteredItems = searched.filter { $0.category == cat.name }
|
|
36
37
|
} else {
|
|
@@ -49,7 +50,7 @@ class HomeViewModel: ObservableObject {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
func toggleFavorite(_ item: Item) {
|
|
52
|
-
|
|
53
|
+
repository.toggleFavorite(item)
|
|
53
54
|
loadData()
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -4,7 +4,7 @@ import SwiftUI
|
|
|
4
4
|
// PLACEHOLDER: Replace sample data with domain-specific content.
|
|
5
5
|
// Keep the singleton pattern and CRUD interface — just change the data.
|
|
6
6
|
|
|
7
|
-
class MockDataProvider: ObservableObject {
|
|
7
|
+
class MockDataProvider: ObservableObject, DataRepository {
|
|
8
8
|
static let shared = MockDataProvider()
|
|
9
9
|
|
|
10
10
|
@Published var items: [Item] = []
|
|
@@ -111,7 +111,7 @@ class AuthManager: ObservableObject {
|
|
|
111
111
|
try await Task.sleep(nanoseconds: 1_000_000_000)
|
|
112
112
|
|
|
113
113
|
await MainActor.run {
|
|
114
|
-
currentUser =
|
|
114
|
+
currentUser = DataSourceResolver.repository.currentUser
|
|
115
115
|
isAuthenticated = true
|
|
116
116
|
isLoading = false
|
|
117
117
|
}
|
|
@@ -136,8 +136,9 @@ class FeedManager: ObservableObject {
|
|
|
136
136
|
try? await Task.sleep(nanoseconds: 500_000_000)
|
|
137
137
|
|
|
138
138
|
await MainActor.run {
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
let repo = DataSourceResolver.repository
|
|
140
|
+
posts = repo.posts
|
|
141
|
+
stories = repo.stories
|
|
141
142
|
isLoading = false
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 posts: [Post] { get }
|
|
14
|
+
var stories: [Story] { get }
|
|
15
|
+
var conversations: [Conversation] { get }
|
|
16
|
+
var notifications: [AppNotification] { get }
|
|
17
|
+
var trendingHashtags: [Hashtag] { get }
|
|
18
|
+
|
|
19
|
+
// MARK: - Helper Functions
|
|
20
|
+
|
|
21
|
+
func comments(for postId: String) -> [Comment]
|
|
22
|
+
|
|
23
|
+
// MARK: - CRUD Operations
|
|
24
|
+
|
|
25
|
+
func addPost(_ post: Post)
|
|
26
|
+
func likePost(_ postId: String)
|
|
27
|
+
func unlikePost(_ postId: String)
|
|
28
|
+
func savePost(_ postId: String)
|
|
29
|
+
func unsavePost(_ postId: String)
|
|
30
|
+
func addComment(_ comment: Comment, to postId: String)
|
|
31
|
+
func likeComment(_ commentId: String, in postId: String)
|
|
32
|
+
func followUser(_ userId: String)
|
|
33
|
+
func unfollowUser(_ userId: String)
|
|
34
|
+
func markNotificationAsRead(_ notificationId: String)
|
|
35
|
+
func sendMessage(_ message: Message, in conversationId: String)
|
|
36
|
+
func reset()
|
|
37
|
+
}
|
|
@@ -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 posts = repo.posts
|
|
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
|
+
}
|
|
@@ -7,7 +7,7 @@ import Combine
|
|
|
7
7
|
class SyncManager: ObservableObject {
|
|
8
8
|
static let shared = SyncManager()
|
|
9
9
|
|
|
10
|
-
private let store =
|
|
10
|
+
private let store: any DataRepository = DataSourceResolver.repository
|
|
11
11
|
private let network = NetworkMonitor.shared
|
|
12
12
|
|
|
13
13
|
@Published var isSyncing: Bool = false
|
package/templates/ios/SocialTemplate/SocialTemplate/Features/CreatePost/CreatePostView.swift
CHANGED
|
@@ -18,7 +18,7 @@ struct CreatePostView: View {
|
|
|
18
18
|
VStack(alignment: .leading, spacing: 16) {
|
|
19
19
|
// User Header
|
|
20
20
|
HStack(spacing: 12) {
|
|
21
|
-
AsyncImage(url: URL(string:
|
|
21
|
+
AsyncImage(url: URL(string: DataSourceResolver.repository.currentUser.avatarURL ?? "")) { image in
|
|
22
22
|
image
|
|
23
23
|
.resizable()
|
|
24
24
|
.aspectRatio(contentMode: .fill)
|
|
@@ -30,7 +30,7 @@ struct CreatePostView: View {
|
|
|
30
30
|
.clipShape(Circle())
|
|
31
31
|
|
|
32
32
|
VStack(alignment: .leading) {
|
|
33
|
-
Text(
|
|
33
|
+
Text(DataSourceResolver.repository.currentUser.username)
|
|
34
34
|
.font(.subheadline)
|
|
35
35
|
.fontWeight(.semibold)
|
|
36
36
|
|
|
@@ -23,7 +23,7 @@ struct CommentsView: View {
|
|
|
23
23
|
|
|
24
24
|
// Comment Input
|
|
25
25
|
HStack(spacing: 12) {
|
|
26
|
-
AsyncImage(url: URL(string:
|
|
26
|
+
AsyncImage(url: URL(string: DataSourceResolver.repository.currentUser.avatarURL ?? "")) { image in
|
|
27
27
|
image
|
|
28
28
|
.resizable()
|
|
29
29
|
.aspectRatio(contentMode: .fill)
|
|
@@ -57,7 +57,7 @@ struct CommentsView: View {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
.onAppear {
|
|
60
|
-
comments =
|
|
60
|
+
comments = DataSourceResolver.repository.comments(for: post.id)
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -58,7 +58,7 @@ struct StoriesBarView: View {
|
|
|
58
58
|
// Add story button
|
|
59
59
|
VStack(spacing: 4) {
|
|
60
60
|
ZStack(alignment: .bottomTrailing) {
|
|
61
|
-
AsyncImage(url: URL(string:
|
|
61
|
+
AsyncImage(url: URL(string: DataSourceResolver.repository.currentUser.avatarURL ?? "")) { image in
|
|
62
62
|
image
|
|
63
63
|
.resizable()
|
|
64
64
|
.aspectRatio(contentMode: .fill)
|
|
@@ -40,7 +40,7 @@ struct MessagesView: View {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
.onAppear {
|
|
43
|
-
conversations =
|
|
43
|
+
conversations = DataSourceResolver.repository.conversations
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -113,7 +113,7 @@ struct ChatView: View {
|
|
|
113
113
|
ForEach(messages) { message in
|
|
114
114
|
MessageBubble(
|
|
115
115
|
message: message,
|
|
116
|
-
isFromCurrentUser: message.senderId ==
|
|
116
|
+
isFromCurrentUser: message.senderId == DataSourceResolver.repository.currentUser.id
|
|
117
117
|
)
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -187,7 +187,7 @@ struct ChatView: View {
|
|
|
187
187
|
messages = (1...20).map { i in
|
|
188
188
|
Message(
|
|
189
189
|
id: "m\(i)",
|
|
190
|
-
senderId: i % 3 == 0 ?
|
|
190
|
+
senderId: i % 3 == 0 ? DataSourceResolver.repository.currentUser.id : conversation.participants.first!.id,
|
|
191
191
|
content: ["Hey!", "How are you?", "Just saw your post!", "That looks amazing!", "Let's catch up soon"][i % 5],
|
|
192
192
|
mediaURL: nil,
|
|
193
193
|
mediaType: .none,
|
|
@@ -200,7 +200,7 @@ struct ChatView: View {
|
|
|
200
200
|
private func sendMessage() {
|
|
201
201
|
let message = Message(
|
|
202
202
|
id: UUID().uuidString,
|
|
203
|
-
senderId:
|
|
203
|
+
senderId: DataSourceResolver.repository.currentUser.id,
|
|
204
204
|
content: newMessage,
|
|
205
205
|
mediaURL: nil,
|
|
206
206
|
mediaType: .none,
|
|
@@ -9,7 +9,7 @@ struct ProfileView: View {
|
|
|
9
9
|
@State private var showEditProfile = false
|
|
10
10
|
|
|
11
11
|
private var isOwnProfile: Bool {
|
|
12
|
-
userId == nil || userId ==
|
|
12
|
+
userId == nil || userId == DataSourceResolver.repository.currentUser.id
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
var body: some View {
|
|
@@ -47,7 +47,8 @@ struct SearchView: View {
|
|
|
47
47
|
return
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
let repo = DataSourceResolver.repository
|
|
51
|
+
searchResults = repo.users.filter {
|
|
51
52
|
$0.username.lowercased().contains(query.lowercased()) ||
|
|
52
53
|
$0.displayName.lowercased().contains(query.lowercased())
|
|
53
54
|
}
|
|
@@ -61,7 +62,7 @@ struct TrendingHashtagsSection: View {
|
|
|
61
62
|
.font(.headline)
|
|
62
63
|
.padding(.horizontal)
|
|
63
64
|
|
|
64
|
-
ForEach(
|
|
65
|
+
ForEach(DataSourceResolver.repository.trendingHashtags.prefix(5)) { hashtag in
|
|
65
66
|
HStack {
|
|
66
67
|
VStack(alignment: .leading, spacing: 2) {
|
|
67
68
|
Text(hashtag.name)
|
|
@@ -103,7 +104,7 @@ struct SuggestedUsersSection: View {
|
|
|
103
104
|
|
|
104
105
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
105
106
|
HStack(spacing: 12) {
|
|
106
|
-
ForEach(
|
|
107
|
+
ForEach(DataSourceResolver.repository.users.prefix(5)) { user in
|
|
107
108
|
SuggestedUserCard(user: user)
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -177,7 +178,7 @@ struct ExploreGridSection: View {
|
|
|
177
178
|
.padding(.horizontal)
|
|
178
179
|
|
|
179
180
|
LazyVGrid(columns: columns, spacing: 2) {
|
|
180
|
-
ForEach(Array(
|
|
181
|
+
ForEach(Array(DataSourceResolver.repository.posts.enumerated()), id: \.element.id) { index, post in
|
|
181
182
|
NavigationLink(destination: PostDetailView(post: post)) {
|
|
182
183
|
AsyncImage(url: URL(string: post.mediaURLs.first ?? "")) { image in
|
|
183
184
|
image
|
|
@@ -104,14 +104,14 @@ struct Conversation: Codable, Identifiable {
|
|
|
104
104
|
if isGroup {
|
|
105
105
|
return groupName ?? participants.map { $0.displayName }.joined(separator: ", ")
|
|
106
106
|
}
|
|
107
|
-
return participants.first { $0.id !=
|
|
107
|
+
return participants.first { $0.id != DataSourceResolver.repository.currentUser.id }?.displayName ?? ""
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
var avatarURL: String? {
|
|
111
111
|
if isGroup {
|
|
112
112
|
return groupAvatarURL
|
|
113
113
|
}
|
|
114
|
-
return participants.first { $0.id !=
|
|
114
|
+
return participants.first { $0.id != DataSourceResolver.repository.currentUser.id }?.avatarURL
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -42,6 +42,15 @@ struct AppConfig {
|
|
|
42
42
|
static let termsURL = "https://example.com/terms"
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// CUSTOMIZE:DATASOURCE - Data source configuration
|
|
46
|
+
struct DataSource {
|
|
47
|
+
enum SourceType {
|
|
48
|
+
case localStorage // Core Data — real local database (DEFAULT)
|
|
49
|
+
case mock // In-memory seed data — for development/testing only
|
|
50
|
+
}
|
|
51
|
+
static let active: SourceType = .mock
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
// CUSTOMIZE:THEME
|
|
46
55
|
struct Theme {
|
|
47
56
|
static let primaryColor = Color(hex: "3B82F6") // Blue
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
/// Views 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 tasks: [TaskItem] { get }
|
|
12
|
+
var projects: [Project] { get }
|
|
13
|
+
var tags: [Tag] { get }
|
|
14
|
+
var kanbanColumns: [KanbanColumn] { get }
|
|
15
|
+
|
|
16
|
+
// MARK: - Task Operations
|
|
17
|
+
|
|
18
|
+
func getTasks() -> [TaskItem]
|
|
19
|
+
func getTask(id: String) -> TaskItem?
|
|
20
|
+
func createTask(_ task: TaskItem)
|
|
21
|
+
func updateTask(_ task: TaskItem)
|
|
22
|
+
func deleteTask(id: String)
|
|
23
|
+
|
|
24
|
+
// MARK: - Project Operations
|
|
25
|
+
|
|
26
|
+
func getProjects() -> [Project]
|
|
27
|
+
func getProject(id: String) -> Project?
|
|
28
|
+
|
|
29
|
+
// MARK: - Query Operations
|
|
30
|
+
|
|
31
|
+
func getTasksByTag(tagId: String) -> [TaskItem]
|
|
32
|
+
func getTasksByDueDate(_ date: Date) -> [TaskItem]
|
|
33
|
+
|
|
34
|
+
// MARK: - Kanban Operations
|
|
35
|
+
|
|
36
|
+
func getKanbanColumns() -> [KanbanColumn]
|
|
37
|
+
func moveTaskToColumn(taskId: String, columnId: String)
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Resolves the active DataRepository implementation based on AppConfig.DataSource.active.
|
|
4
|
+
/// Views use this to obtain the correct data source without knowing the concrete type.
|
|
5
|
+
///
|
|
6
|
+
/// Usage:
|
|
7
|
+
/// let repo = DataSourceResolver.repository
|
|
8
|
+
/// let tasks = repo.tasks
|
|
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
|
+
}
|
|
@@ -18,7 +18,9 @@ struct CalendarEvent: Identifiable {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
struct CalendarView: View {
|
|
21
|
-
@StateObject private var dataProvider =
|
|
21
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
22
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
23
|
+
}()
|
|
22
24
|
@State private var selectedDate = Date()
|
|
23
25
|
@State private var currentMonth = Date()
|
|
24
26
|
|
|
@@ -179,7 +181,9 @@ struct CalendarView: View {
|
|
|
179
181
|
|
|
180
182
|
struct EventCardView: View {
|
|
181
183
|
let event: CalendarEvent
|
|
182
|
-
@StateObject private var dataProvider =
|
|
184
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
185
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
186
|
+
}()
|
|
183
187
|
|
|
184
188
|
private var task: TaskItem? {
|
|
185
189
|
dataProvider.getTask(id: event.taskId)
|
|
@@ -104,7 +104,8 @@ struct TaskRowView: View {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
if !task.tags.isEmpty {
|
|
107
|
-
let
|
|
107
|
+
let repository = DataSourceResolver.repository as! MockDataProvider
|
|
108
|
+
let tags = repository.tags.filter { task.tags.contains($0.id) }
|
|
108
109
|
TagChipListView(tags: tags)
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import SwiftUI
|
|
2
2
|
|
|
3
3
|
struct KanbanBoardView: View {
|
|
4
|
-
@StateObject private var dataProvider =
|
|
4
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
5
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
6
|
+
}()
|
|
5
7
|
@State private var draggedTask: Task?
|
|
6
8
|
|
|
7
9
|
private var columns: [KanbanColumn] {
|
|
@@ -27,7 +29,9 @@ struct KanbanBoardView: View {
|
|
|
27
29
|
struct KanbanColumnView: View {
|
|
28
30
|
let column: KanbanColumn
|
|
29
31
|
@Binding var draggedTask: Task?
|
|
30
|
-
@StateObject private var dataProvider =
|
|
32
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
33
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
34
|
+
}()
|
|
31
35
|
|
|
32
36
|
private var columnTasks: [Task] {
|
|
33
37
|
dataProvider.getTasks()
|
|
@@ -96,7 +100,9 @@ struct KanbanColumnView: View {
|
|
|
96
100
|
|
|
97
101
|
struct KanbanTaskCard: View {
|
|
98
102
|
let task: Task
|
|
99
|
-
@StateObject private var dataProvider =
|
|
103
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
104
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
105
|
+
}()
|
|
100
106
|
|
|
101
107
|
var body: some View {
|
|
102
108
|
VStack(alignment: .leading, spacing: 8) {
|
|
@@ -2,7 +2,9 @@ import SwiftUI
|
|
|
2
2
|
|
|
3
3
|
struct ProjectDetailView: View {
|
|
4
4
|
let project: Project
|
|
5
|
-
@StateObject private var dataProvider =
|
|
5
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
6
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
7
|
+
}()
|
|
6
8
|
@State private var searchText = ""
|
|
7
9
|
@State private var showingAddTask = false
|
|
8
10
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import SwiftUI
|
|
2
2
|
|
|
3
3
|
struct ProjectListView: View {
|
|
4
|
-
@StateObject private var dataProvider =
|
|
4
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
5
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
6
|
+
}()
|
|
5
7
|
@State private var searchText = ""
|
|
6
8
|
@State private var showingAddProject = false
|
|
7
9
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import SwiftUI
|
|
2
2
|
|
|
3
3
|
struct SearchView: View {
|
|
4
|
-
@StateObject private var dataProvider =
|
|
4
|
+
@StateObject private var dataProvider: MockDataProvider = {
|
|
5
|
+
DataSourceResolver.repository as! MockDataProvider
|
|
6
|
+
}()
|
|
5
7
|
@State private var searchText = ""
|
|
6
8
|
@State private var selectedFilter: SearchFilter = .all
|
|
7
9
|
|