@shaykec/app-agent 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/package.json +1 -1
  2. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/AppConfig.kt +9 -0
  3. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/data/DataSourceResolver.kt +23 -0
  4. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +1 -1
  5. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/ProviderDetailScreen.kt +1 -1
  6. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/SearchScreen.kt +1 -1
  7. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/mock/MockDataProvider.kt +12 -11
  8. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/AppConfig.kt +9 -0
  9. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataRepository.kt +33 -0
  10. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +23 -0
  11. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/AppConfig.kt +9 -0
  12. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataRepository.kt +62 -0
  13. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataSourceResolver.kt +23 -0
  14. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt +28 -28
  15. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/AppConfig.kt +9 -0
  16. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataRepository.kt +30 -0
  17. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataSourceResolver.kt +23 -0
  18. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/create/CreateScreen.kt +3 -2
  19. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/detail/DetailScreen.kt +4 -3
  20. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/explore/ExploreScreen.kt +7 -6
  21. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/favorites/FavoritesScreen.kt +4 -3
  22. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/home/HomeScreen.kt +5 -4
  23. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/mock/MockDataProvider.kt +12 -11
  24. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/AppConfig.kt +9 -0
  25. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +38 -0
  26. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataSourceResolver.kt +23 -0
  27. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/FeedScreen.kt +2 -2
  28. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/profile/ProfileScreen.kt +1 -1
  29. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +1 -1
  30. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/mock/MockDataProvider.kt +7 -6
  31. package/templates/ios/BookTemplate/BookTemplate/App/AppConfig.swift +12 -2
  32. package/templates/ios/BookTemplate/BookTemplate/Data/DataRepository.swift +33 -0
  33. package/templates/ios/BookTemplate/BookTemplate/Data/DataSourceResolver.swift +20 -0
  34. package/templates/ios/BookTemplate/BookTemplate/Features/Booking/BookingView.swift +1 -1
  35. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/DiscoveryView.swift +3 -3
  36. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/MapExploreView.swift +1 -1
  37. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/ProviderDetailView.swift +1 -1
  38. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/SearchView.swift +2 -2
  39. package/templates/ios/BookTemplate/BookTemplate/MockData/MockDataProvider.swift +18 -1
  40. package/templates/ios/MapTemplate/MapTemplate/App/AppConfig.swift +11 -9
  41. package/templates/ios/MapTemplate/MapTemplate/Data/DataSourceResolver.swift +20 -0
  42. package/templates/ios/MapTemplate/MapTemplate/Features/Favorites/FavoritesView.swift +1 -1
  43. package/templates/ios/MapTemplate/MapTemplate/Features/PlaceDetail/PlaceDetailView.swift +2 -2
  44. package/templates/ios/MapTemplate/MapTemplate/Features/Profile/ProfileView.swift +1 -1
  45. package/templates/ios/MapTemplate/MapTemplate/Features/Route/RouteView.swift +1 -1
  46. package/templates/ios/MapTemplate/MapTemplate/Features/Search/SearchView.swift +1 -1
  47. package/templates/ios/ShopTemplate/ShopTemplate/App/AppConfig.swift +9 -0
  48. package/templates/ios/ShopTemplate/ShopTemplate/Data/DataSourceResolver.swift +20 -0
  49. package/templates/ios/ShopTemplate/ShopTemplate/Features/Home/HomeView.swift +9 -7
  50. package/templates/ios/ShopTemplate/ShopTemplate/Features/Orders/OrdersView.swift +7 -3
  51. package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductDetailView.swift +4 -2
  52. package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductListView.swift +6 -3
  53. package/templates/ios/ShopTemplate/ShopTemplate/MockData/MockDataProvider.swift +1 -1
  54. package/templates/ios/Skeleton/Skeleton/App/AppConfig.swift +9 -0
  55. package/templates/ios/Skeleton/Skeleton/Data/DataRepository.swift +30 -0
  56. package/templates/ios/Skeleton/Skeleton/Data/DataSourceResolver.swift +20 -0
  57. package/templates/ios/Skeleton/Skeleton/Features/Explore/ExploreViewModel.swift +7 -6
  58. package/templates/ios/Skeleton/Skeleton/Features/Home/HomeViewModel.swift +8 -7
  59. package/templates/ios/Skeleton/Skeleton/MockData/MockDataProvider.swift +1 -1
  60. package/templates/ios/SocialTemplate/SocialTemplate/App/AppConfig.swift +4 -3
  61. package/templates/ios/SocialTemplate/SocialTemplate/Data/DataRepository.swift +37 -0
  62. package/templates/ios/SocialTemplate/SocialTemplate/Data/DataSourceResolver.swift +20 -0
  63. package/templates/ios/SocialTemplate/SocialTemplate/Data/SyncManager.swift +1 -1
  64. package/templates/ios/SocialTemplate/SocialTemplate/Features/CreatePost/CreatePostView.swift +2 -2
  65. package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/CommentsView.swift +2 -2
  66. package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/FeedView.swift +1 -1
  67. package/templates/ios/SocialTemplate/SocialTemplate/Features/Messages/MessagesView.swift +4 -4
  68. package/templates/ios/SocialTemplate/SocialTemplate/Features/Notifications/NotificationsView.swift +1 -1
  69. package/templates/ios/SocialTemplate/SocialTemplate/Features/Profile/ProfileView.swift +1 -1
  70. package/templates/ios/SocialTemplate/SocialTemplate/Features/Search/SearchView.swift +5 -4
  71. package/templates/ios/SocialTemplate/SocialTemplate/MockData/MockDataProvider.swift +1 -1
  72. package/templates/ios/SocialTemplate/SocialTemplate/Models/Models.swift +2 -2
@@ -159,7 +159,7 @@ struct PlaceDetailView: View {
159
159
  .foregroundColor(AppConfig.Theme.primaryColor)
160
160
  }
161
161
 
162
- let placeReviews = MockDataProvider.shared.getReviews(for: place.id)
162
+ let placeReviews = DataSourceResolver.repository.getReviews(for: place.id)
163
163
  ForEach(Array(placeReviews.prefix(3).enumerated()), id: \.element.id) { index, review in
164
164
  ReviewCardView(review: review, index: index)
165
165
  .accessibilityIdentifier("place_detail_review_\(index)_card")
@@ -269,7 +269,7 @@ struct ReviewsListView: View {
269
269
  }
270
270
 
271
271
  #Preview {
272
- PlaceDetailView(place: MockDataProvider.shared.places.first!)
272
+ PlaceDetailView(place: DataSourceResolver.repository.places.first!)
273
273
  .environmentObject(MapManager())
274
274
  .environmentObject(AppState())
275
275
  }
@@ -2,7 +2,7 @@ import SwiftUI
2
2
 
3
3
  struct ProfileView: View {
4
4
  @EnvironmentObject var appState: AppState
5
- @State private var user = MockDataProvider.shared.currentUser
5
+ @State private var user = DataSourceResolver.repository.currentUser
6
6
 
7
7
  var body: some View {
8
8
  NavigationStack {
@@ -94,7 +94,7 @@ struct RouteView: View {
94
94
 
95
95
  private func loadRoute() {
96
96
  isLoading = true
97
- route = MockDataProvider.shared.getRoute(from: origin, to: destination)
97
+ route = DataSourceResolver.repository.getRoute(from: origin, to: destination)
98
98
  isLoading = false
99
99
  }
100
100
  }
@@ -20,7 +20,7 @@ struct SearchView: View {
20
20
  .accessibilityIdentifier("search_field")
21
21
  .onChange(of: searchText) { newValue in
22
22
  if !newValue.isEmpty {
23
- suggestions = MockDataProvider.shared.searchPlaces(query: newValue)
23
+ suggestions = DataSourceResolver.repository.searchPlaces(query: newValue)
24
24
  } else {
25
25
  suggestions = []
26
26
  }
@@ -108,6 +108,15 @@ struct AppConfig {
108
108
  ("Fast Delivery", "Get your orders delivered right to your doorstep", "shippingbox.fill")
109
109
  ]
110
110
  }
111
+
112
+ // CUSTOMIZE:DATASOURCE - Data source configuration
113
+ struct DataSource {
114
+ enum SourceType {
115
+ case localStorage // Core Data — real local database (DEFAULT)
116
+ case mock // In-memory seed data — for development/testing only
117
+ }
118
+ static let active: SourceType = .mock
119
+ }
111
120
  }
112
121
 
113
122
  // MARK: - App State
@@ -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 products = repo.products
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
+ }
@@ -154,16 +154,18 @@ class HomeViewModel: ObservableObject {
154
154
  var id: Self { self }
155
155
  }
156
156
 
157
- init() {
157
+ private let repository: any DataRepository
158
+
159
+ init(repository: any DataRepository = DataSourceResolver.repository) {
160
+ self.repository = repository
158
161
  loadData()
159
162
  }
160
163
 
161
164
  func loadData() {
162
- let mock = MockDataProvider.shared
163
- banners = mock.banners
164
- categories = mock.categories
165
- featuredProducts = mock.featuredProducts()
166
- saleProducts = mock.saleProducts()
165
+ banners = repository.banners
166
+ categories = repository.categories
167
+ featuredProducts = repository.featuredProducts()
168
+ saleProducts = repository.saleProducts()
167
169
  loadRecentlyViewed()
168
170
  }
169
171
 
@@ -179,7 +181,7 @@ class HomeViewModel: ObservableObject {
179
181
  // Load from UserDefaults
180
182
  if let data = UserDefaults.standard.data(forKey: "recentlyViewedIds"),
181
183
  let ids = try? JSONDecoder().decode([String].self, from: data) {
182
- recentlyViewed = ids.compactMap { MockDataProvider.shared.product(by: $0) }
184
+ recentlyViewed = ids.compactMap { repository.product(by: $0) }
183
185
  }
184
186
  }
185
187
 
@@ -42,15 +42,19 @@ class OrdersViewModel: ObservableObject {
42
42
  @Published var orders: [Order] = []
43
43
  @Published var isLoading = true
44
44
 
45
- init() {
45
+ private let repository: any DataRepository
46
+
47
+ init(repository: any DataRepository = DataSourceResolver.repository) {
48
+ self.repository = repository
46
49
  loadOrders()
47
50
  }
48
51
 
49
52
  func loadOrders() {
50
53
  // CUSTOMIZE:API - Replace with actual API call
51
54
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
52
- self?.orders = MockDataProvider.shared.orders
53
- self?.isLoading = false
55
+ guard let self = self else { return }
56
+ self.orders = self.repository.orders
57
+ self.isLoading = false
54
58
  }
55
59
  }
56
60
  }
@@ -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 = MockDataProvider.shared.reviews(for: product.id)
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
- init(category: Category?, products: [Product]? = nil) {
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 = MockDataProvider.shared.products(for: category)
243
+ self.products = self.repository.products(for: category)
241
244
  } else {
242
- self.products = MockDataProvider.shared.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 dataProvider = MockDataProvider.shared
9
+ private let repository: any DataRepository
10
10
 
11
- init() {
12
- categories = dataProvider.categories
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 = dataProvider.searchItems(query: query)
17
+ searchResults = repository.searchItems(query: query)
17
18
  }
18
19
 
19
20
  func itemsForCategory(_ name: String) -> [Item] {
20
- dataProvider.itemsByCategory(name)
21
+ repository.itemsByCategory(name)
21
22
  }
22
23
 
23
24
  func toggleFavorite(_ item: Item) {
24
- dataProvider.toggleFavorite(item)
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 dataProvider = MockDataProvider.shared
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 = dataProvider.items
20
- categories = dataProvider.categories
20
+ items = repository.items
21
+ categories = repository.categories
21
22
  filteredItems = items
22
23
  }
23
24
 
24
25
  func refresh() {
25
- dataProvider.loadData()
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 = dataProvider.searchItems(query: query)
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
- dataProvider.toggleFavorite(item)
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 = MockDataProvider.shared.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
- posts = MockDataProvider.shared.posts
140
- stories = MockDataProvider.shared.stories
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 = MockDataProvider.shared
10
+ private let store: any DataRepository = DataSourceResolver.repository
11
11
  private let network = NetworkMonitor.shared
12
12
 
13
13
  @Published var isSyncing: Bool = false
@@ -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: MockDataProvider.shared.currentUser.avatarURL ?? "")) { image in
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(MockDataProvider.shared.currentUser.username)
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: MockDataProvider.shared.currentUser.avatarURL ?? "")) { image in
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 = MockDataProvider.shared.comments(for: post.id)
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: MockDataProvider.shared.currentUser.avatarURL ?? "")) { image in
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 = MockDataProvider.shared.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 == MockDataProvider.shared.currentUser.id
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 ? MockDataProvider.shared.currentUser.id : conversation.participants.first!.id,
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: MockDataProvider.shared.currentUser.id,
203
+ senderId: DataSourceResolver.repository.currentUser.id,
204
204
  content: newMessage,
205
205
  mediaURL: nil,
206
206
  mediaType: .none,
@@ -30,7 +30,7 @@ struct NotificationsView: View {
30
30
  }
31
31
  .navigationTitle("Activity")
32
32
  .onAppear {
33
- notifications = MockDataProvider.shared.notifications
33
+ notifications = DataSourceResolver.repository.notifications
34
34
  }
35
35
  }
36
36
  }
@@ -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 == MockDataProvider.shared.currentUser.id
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
- searchResults = MockDataProvider.shared.users.filter {
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(MockDataProvider.shared.trendingHashtags.prefix(5)) { hashtag in
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(MockDataProvider.shared.users.prefix(5)) { user in
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(MockDataProvider.shared.posts.enumerated()), id: \.element.id) { index, post in
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
@@ -1,7 +1,7 @@
1
1
  import Foundation
2
2
  import Combine
3
3
 
4
- class MockDataProvider: ObservableObject {
4
+ class MockDataProvider: ObservableObject, DataRepository {
5
5
  static let shared = MockDataProvider()
6
6
 
7
7
  // MARK: - Published Mutable State
@@ -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 != MockDataProvider.shared.currentUser.id }?.displayName ?? ""
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 != MockDataProvider.shared.currentUser.id }?.avatarURL
114
+ return participants.first { $0.id != DataSourceResolver.repository.currentUser.id }?.avatarURL
115
115
  }
116
116
  }
117
117