@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaykec/app-agent",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "AI-powered mobile app generator using Cursor CLI and templates",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,6 +45,15 @@ object AppConfig {
45
45
  const val CANCELLATION_POLICY_URL = "https://example.com/cancellation"
46
46
  }
47
47
 
48
+ // CUSTOMIZE:DATASOURCE - Data source configuration
49
+ object DataSource {
50
+ enum class SourceType {
51
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
52
+ MOCK // In-memory seed data — for development/testing only
53
+ }
54
+ val active: SourceType = SourceType.MOCK
55
+ }
56
+
48
57
  // CUSTOMIZE:CATEGORIES
49
58
  data class Category(
50
59
  val id: String,
@@ -0,0 +1,23 @@
1
+ package com.appship.book.data
2
+
3
+ import com.appship.book.AppConfig
4
+ import com.appship.book.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 providers by repository.providers.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
+ }
@@ -22,11 +22,11 @@ import androidx.compose.ui.draw.clip
22
22
  import androidx.compose.ui.platform.testTag
23
23
  import androidx.compose.ui.text.font.FontWeight
24
24
  import androidx.compose.ui.unit.dp
25
+ import com.appship.book.data.DataSourceResolver
25
26
  import com.appship.book.data.Provider
26
27
  import com.appship.book.data.Service
27
28
  import com.appship.book.data.TimeSlot
28
29
  import com.appship.book.data.formatCurrency
29
- import com.appship.book.mock.MockDataProvider
30
30
  import java.time.LocalDate
31
31
  import java.time.format.DateTimeFormatter
32
32
 
@@ -17,10 +17,10 @@ import androidx.compose.ui.platform.testTag
17
17
  import androidx.compose.ui.text.font.FontWeight
18
18
  import androidx.compose.ui.unit.dp
19
19
  import coil.compose.AsyncImage
20
+ import com.appship.book.data.DataSourceResolver
20
21
  import com.appship.book.data.Provider
21
22
  import com.appship.book.data.Review
22
23
  import com.appship.book.data.Service
23
- import com.appship.book.mock.MockDataProvider
24
24
 
25
25
  @OptIn(ExperimentalMaterial3Api::class)
26
26
  @Composable
@@ -18,8 +18,8 @@ import androidx.compose.ui.platform.testTag
18
18
  import androidx.compose.ui.text.font.FontWeight
19
19
  import androidx.compose.ui.unit.dp
20
20
  import com.appship.book.AppConfig
21
+ import com.appship.book.data.DataSourceResolver
21
22
  import com.appship.book.data.Provider
22
- import com.appship.book.mock.MockDataProvider
23
23
 
24
24
  @OptIn(ExperimentalMaterial3Api::class)
25
25
  @Composable
@@ -1,13 +1,14 @@
1
1
  package com.appship.book.mock
2
2
 
3
3
  import com.appship.book.data.*
4
+ import com.appship.book.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 defaultProviders = listOf(
13
14
  Provider(
@@ -162,7 +163,7 @@ object MockDataProvider {
162
163
  )
163
164
 
164
165
  private val _providers = MutableStateFlow(defaultProviders)
165
- val providers: StateFlow<List<Provider>> = _providers.asStateFlow()
166
+ override val providers: StateFlow<List<Provider>> = _providers.asStateFlow()
166
167
 
167
168
  // Legacy accessor for backward compatibility
168
169
  val providersList: List<Provider> get() = _providers.value
@@ -223,7 +224,7 @@ object MockDataProvider {
223
224
  val upcomingBookingsList: List<Booking> get() = _upcomingBookings.value
224
225
  val pastBookingsList: List<Booking> get() = _pastBookings.value
225
226
 
226
- fun getTimeSlots(date: LocalDate): List<TimeSlot> {
227
+ override fun getTimeSlots(date: LocalDate): List<TimeSlot> {
227
228
  val times = listOf(
228
229
  "9:00 AM", "9:30 AM", "10:00 AM", "10:30 AM", "11:00 AM", "11:30 AM",
229
230
  "12:00 PM", "12:30 PM", "1:00 PM", "1:30 PM", "2:00 PM", "2:30 PM",
@@ -234,7 +235,7 @@ object MockDataProvider {
234
235
  }
235
236
  }
236
237
 
237
- fun getReviews(providerId: String): List<Review> {
238
+ override fun getReviews(providerId: String): List<Review> {
238
239
  return (1..10).map { i ->
239
240
  Review(
240
241
  id = "r$providerId-$i",
@@ -250,7 +251,7 @@ object MockDataProvider {
250
251
  }
251
252
  }
252
253
 
253
- fun searchProviders(query: String): List<Provider> {
254
+ override fun searchProviders(query: String): List<Provider> {
254
255
  if (query.isBlank()) return _providers.value
255
256
  val q = query.lowercase()
256
257
  return _providers.value.filter {
@@ -261,7 +262,7 @@ object MockDataProvider {
261
262
  }
262
263
 
263
264
  // CRUD Operations for Bookings
264
- fun createBooking(booking: Booking) {
265
+ override fun createBooking(booking: Booking) {
265
266
  when (booking.status) {
266
267
  BookingStatus.PENDING, BookingStatus.CONFIRMED -> {
267
268
  _upcomingBookings.value = _upcomingBookings.value + booking
@@ -273,7 +274,7 @@ object MockDataProvider {
273
274
  SyncManager.incrementPending()
274
275
  }
275
276
 
276
- fun cancelBooking(bookingId: String) {
277
+ override fun cancelBooking(bookingId: String) {
277
278
  val booking = _upcomingBookings.value.find { it.id == bookingId }
278
279
  if (booking != null) {
279
280
  _upcomingBookings.value = _upcomingBookings.value.filter { it.id != bookingId }
@@ -285,7 +286,7 @@ object MockDataProvider {
285
286
  }
286
287
  }
287
288
 
288
- fun completeBooking(bookingId: String) {
289
+ override fun completeBooking(bookingId: String) {
289
290
  val booking = _upcomingBookings.value.find { it.id == bookingId }
290
291
  if (booking != null) {
291
292
  _upcomingBookings.value = _upcomingBookings.value.filter { it.id != bookingId }
@@ -298,19 +299,19 @@ object MockDataProvider {
298
299
  }
299
300
 
300
301
  // CRUD Operations for Reviews
301
- fun addReview(providerId: String, review: Review) {
302
+ override fun addReview(providerId: String, review: Review) {
302
303
  // Reviews are generated dynamically, but we can track them for sync
303
304
  SyncManager.incrementPending()
304
305
  }
305
306
 
306
307
  // CRUD Operations for Providers
307
- fun toggleFavoriteProvider(providerId: String) {
308
+ override fun toggleFavoriteProvider(providerId: String) {
308
309
  // Note: Provider model doesn't have isFavorite field, but we can add it if needed
309
310
  // For now, this is a placeholder for sync tracking
310
311
  SyncManager.incrementPending()
311
312
  }
312
313
 
313
- fun reset() {
314
+ override fun reset() {
314
315
  _providers.value = defaultProviders
315
316
  _upcomingBookings.value = defaultUpcomingBookings
316
317
  _pastBookings.value = defaultPastBookings
@@ -75,4 +75,13 @@ object AppConfig {
75
75
  OnboardingSlide("Get Directions", "Navigate easily with turn-by-turn directions", "directions"),
76
76
  OnboardingSlide("Save Favorites", "Keep track of your favorite places", "favorite")
77
77
  )
78
+
79
+ // CUSTOMIZE:DATASOURCE - Data source configuration
80
+ object DataSource {
81
+ enum class SourceType {
82
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
83
+ MOCK // In-memory seed data — for development/testing only
84
+ }
85
+ val active: SourceType = SourceType.MOCK
86
+ }
78
87
  }
@@ -0,0 +1,33 @@
1
+ package com.appship.map.data
2
+
3
+ /**
4
+ * Abstracts the data layer. Both MockDataProvider and LocalStorageProvider
5
+ * implement this, allowing the app to swap data sources via AppConfig.DataSource.active.
6
+ *
7
+ * Screens and ViewModels depend on this interface rather than on a concrete implementation.
8
+ * Use [DataSourceResolver.repository] to get the active provider.
9
+ */
10
+ interface DataRepository {
11
+ // Observable state
12
+ val places: List<Place>
13
+ val reviews: Map<String, List<Review>>
14
+ val routes: List<Route>
15
+ val currentUser: User
16
+
17
+ // CRUD operations
18
+ fun getPlace(id: String): Place?
19
+ fun getReviews(placeId: String): List<Review>
20
+ fun addReview(review: Review, placeId: String)
21
+ fun toggleFavorite(place: Place)
22
+ fun getFavoritePlaces(): List<Place>
23
+
24
+ // Filtering & Search
25
+ fun filterPlaces(criteria: FilterCriteria): List<Place>
26
+ fun searchPlaces(query: String): List<Place>
27
+
28
+ // Route operations
29
+ fun getRoute(origin: Coordinate, destination: Coordinate): Route?
30
+
31
+ // Reset
32
+ fun reset()
33
+ }
@@ -0,0 +1,23 @@
1
+ package com.appship.map.data
2
+
3
+ import com.appship.map.AppConfig
4
+ import com.appship.map.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 places = repository.places
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
+ }
@@ -76,4 +76,13 @@ object AppConfig {
76
76
  )
77
77
  )
78
78
  }
79
+
80
+ // CUSTOMIZE:DATASOURCE - Data source configuration
81
+ object DataSource {
82
+ enum class SourceType {
83
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
84
+ MOCK // In-memory seed data — for development/testing only
85
+ }
86
+ val active: SourceType = SourceType.MOCK
87
+ }
79
88
  }
@@ -0,0 +1,62 @@
1
+ package com.appship.shop.data
2
+
3
+ import com.appship.shop.data.models.*
4
+ import kotlinx.coroutines.flow.StateFlow
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 categories: StateFlow<List<Category>>
16
+ val products: StateFlow<List<Product>>
17
+ val banners: StateFlow<List<Banner>>
18
+ val favorites: StateFlow<Set<String>>
19
+ val orders: StateFlow<List<Order>>
20
+
21
+ // CRUD operations - Categories
22
+ fun addCategory(category: Category)
23
+ fun updateCategory(category: Category)
24
+ fun deleteCategory(categoryId: String)
25
+
26
+ // CRUD operations - Products
27
+ fun addProduct(product: Product)
28
+ fun updateProduct(product: Product)
29
+ fun deleteProduct(productId: String)
30
+
31
+ // CRUD operations - Banners
32
+ fun addBanner(banner: Banner)
33
+ fun updateBanner(banner: Banner)
34
+ fun deleteBanner(bannerId: String)
35
+
36
+ // CRUD operations - Favorites
37
+ fun addToFavorites(productId: String)
38
+ fun removeFromFavorites(productId: String)
39
+ fun isFavorite(productId: String): Boolean
40
+
41
+ // CRUD operations - Orders
42
+ fun addOrder(order: Order)
43
+ fun updateOrder(order: Order)
44
+ fun deleteOrder(orderId: String)
45
+ fun getOrder(orderId: String): Order?
46
+
47
+ // Reset
48
+ fun reset()
49
+
50
+ // Helper functions
51
+ fun getProduct(id: String): Product?
52
+ fun getProductsByCategory(categoryId: String): List<Product>
53
+ fun searchProducts(query: String): List<Product>
54
+ fun getFeaturedProducts(): List<Product>
55
+ fun getSaleProducts(): List<Product>
56
+ fun getReviews(productId: String): List<Review>
57
+
58
+ // Static data
59
+ val shippingOptions: List<ShippingOption>
60
+ val storeLocations: List<StoreLocation>
61
+ val sampleUser: User
62
+ }
@@ -0,0 +1,23 @@
1
+ package com.appship.shop.data
2
+
3
+ import com.appship.shop.AppConfig
4
+ import com.appship.shop.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 products by repository.products.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
+ }
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
9
9
  import kotlinx.coroutines.flow.asStateFlow
10
10
 
11
11
  // CUSTOMIZE:API - Replace mock data with real API calls
12
- object MockDataProvider {
12
+ object MockDataProvider : DataRepository {
13
13
 
14
14
  // MARK: - Default Seed Data
15
15
  private val defaultCategories = listOf(
@@ -258,28 +258,28 @@ object MockDataProvider {
258
258
 
259
259
  // MARK: - StateFlow Properties
260
260
  private val _categories = MutableStateFlow(defaultCategories)
261
- val categories: StateFlow<List<Category>> = _categories.asStateFlow()
261
+ override val categories: StateFlow<List<Category>> = _categories.asStateFlow()
262
262
 
263
263
  private val _products = MutableStateFlow(defaultProducts)
264
- val products: StateFlow<List<Product>> = _products.asStateFlow()
264
+ override val products: StateFlow<List<Product>> = _products.asStateFlow()
265
265
 
266
266
  private val _banners = MutableStateFlow(defaultBanners)
267
- val banners: StateFlow<List<Banner>> = _banners.asStateFlow()
267
+ override val banners: StateFlow<List<Banner>> = _banners.asStateFlow()
268
268
 
269
269
  private val _favorites = MutableStateFlow<Set<String>>(emptySet())
270
- val favorites: StateFlow<Set<String>> = _favorites.asStateFlow()
270
+ override val favorites: StateFlow<Set<String>> = _favorites.asStateFlow()
271
271
 
272
272
  private val _orders = MutableStateFlow<List<Order>>(emptyList())
273
- val orders: StateFlow<List<Order>> = _orders.asStateFlow()
273
+ override val orders: StateFlow<List<Order>> = _orders.asStateFlow()
274
274
 
275
275
  // MARK: - Static Data (unchanged)
276
- val shippingOptions = listOf(
276
+ override val shippingOptions = listOf(
277
277
  ShippingOption("s1", "Standard Shipping", "5-7 business days", AppConfig.Business.STANDARD_SHIPPING_COST, "5-7 business days", "local_shipping"),
278
278
  ShippingOption("s2", "Express Shipping", "2-3 business days", AppConfig.Business.EXPRESS_SHIPPING_COST, "2-3 business days", "local_shipping"),
279
279
  ShippingOption("s3", "Same Day Delivery", "Order before 2 PM", 19.99, "Today", "bolt")
280
280
  )
281
281
 
282
- val storeLocations = listOf(
282
+ override val storeLocations = listOf(
283
283
  StoreLocation(
284
284
  id = "store1",
285
285
  name = "Downtown Store",
@@ -344,78 +344,78 @@ object MockDataProvider {
344
344
  // MARK: - CRUD Functions
345
345
 
346
346
  // Categories
347
- fun addCategory(category: Category) {
347
+ override fun addCategory(category: Category) {
348
348
  _categories.value = _categories.value + category
349
349
  }
350
350
 
351
- fun updateCategory(category: Category) {
351
+ override fun updateCategory(category: Category) {
352
352
  _categories.value = _categories.value.map { if (it.id == category.id) category else it }
353
353
  }
354
354
 
355
- fun deleteCategory(categoryId: String) {
355
+ override fun deleteCategory(categoryId: String) {
356
356
  _categories.value = _categories.value.filter { it.id != categoryId }
357
357
  }
358
358
 
359
359
  // Products
360
- fun addProduct(product: Product) {
360
+ override fun addProduct(product: Product) {
361
361
  _products.value = _products.value + product
362
362
  }
363
363
 
364
- fun updateProduct(product: Product) {
364
+ override fun updateProduct(product: Product) {
365
365
  _products.value = _products.value.map { if (it.id == product.id) product else it }
366
366
  }
367
367
 
368
- fun deleteProduct(productId: String) {
368
+ override fun deleteProduct(productId: String) {
369
369
  _products.value = _products.value.filter { it.id != productId }
370
370
  }
371
371
 
372
372
  // Banners
373
- fun addBanner(banner: Banner) {
373
+ override fun addBanner(banner: Banner) {
374
374
  _banners.value = _banners.value + banner
375
375
  }
376
376
 
377
- fun updateBanner(banner: Banner) {
377
+ override fun updateBanner(banner: Banner) {
378
378
  _banners.value = _banners.value.map { if (it.id == banner.id) banner else it }
379
379
  }
380
380
 
381
- fun deleteBanner(bannerId: String) {
381
+ override fun deleteBanner(bannerId: String) {
382
382
  _banners.value = _banners.value.filter { it.id != bannerId }
383
383
  }
384
384
 
385
385
  // Favorites
386
- fun addToFavorites(productId: String) {
386
+ override fun addToFavorites(productId: String) {
387
387
  val newFavorites = _favorites.value + productId
388
388
  _favorites.value = newFavorites
389
389
  SyncManager.addPendingFavorite(productId)
390
390
  }
391
391
 
392
- fun removeFromFavorites(productId: String) {
392
+ override fun removeFromFavorites(productId: String) {
393
393
  val newFavorites = _favorites.value - productId
394
394
  _favorites.value = newFavorites
395
395
  SyncManager.removePendingFavorite(productId)
396
396
  }
397
397
 
398
- fun isFavorite(productId: String): Boolean = productId in _favorites.value
398
+ override fun isFavorite(productId: String): Boolean = productId in _favorites.value
399
399
 
400
400
  // Orders
401
- fun addOrder(order: Order) {
401
+ override fun addOrder(order: Order) {
402
402
  _orders.value = _orders.value + order
403
403
  SyncManager.addPendingOrder(order)
404
404
  }
405
405
 
406
- fun updateOrder(order: Order) {
406
+ override fun updateOrder(order: Order) {
407
407
  _orders.value = _orders.value.map { if (it.id == order.id) order else it }
408
408
  }
409
409
 
410
- fun deleteOrder(orderId: String) {
410
+ override fun deleteOrder(orderId: String) {
411
411
  _orders.value = _orders.value.filter { it.id != orderId }
412
412
  SyncManager.removePendingOrder(orderId)
413
413
  }
414
414
 
415
- fun getOrder(orderId: String): Order? = _orders.value.find { it.id == orderId }
415
+ override fun getOrder(orderId: String): Order? = _orders.value.find { it.id == orderId }
416
416
 
417
417
  // MARK: - Reset
418
- fun reset() {
418
+ override fun reset() {
419
419
  _categories.value = defaultCategories
420
420
  _products.value = defaultProducts
421
421
  _banners.value = defaultBanners
@@ -425,12 +425,12 @@ object MockDataProvider {
425
425
  }
426
426
 
427
427
  // MARK: - Helper Functions
428
- fun getProduct(id: String): Product? = _products.value.find { it.id == id }
428
+ override fun getProduct(id: String): Product? = _products.value.find { it.id == id }
429
429
 
430
- fun getProductsByCategory(categoryId: String): List<Product> =
430
+ override fun getProductsByCategory(categoryId: String): List<Product> =
431
431
  _products.value.filter { it.category.id == categoryId }
432
432
 
433
- fun searchProducts(query: String): List<Product> {
433
+ override fun searchProducts(query: String): List<Product> {
434
434
  if (query.isBlank()) return _products.value
435
435
  val q = query.lowercase()
436
436
  return _products.value.filter {
@@ -49,6 +49,15 @@ object AppConfig {
49
49
  val profile: ImageVector = Icons.Filled.Person
50
50
  }
51
51
 
52
+ // CUSTOMIZE:DATASOURCE - Data source configuration
53
+ object DataSource {
54
+ enum class SourceType {
55
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
56
+ MOCK // In-memory seed data — for development/testing only
57
+ }
58
+ val active: SourceType = SourceType.MOCK
59
+ }
60
+
52
61
  // CUSTOMIZE:CATEGORIES - Domain categories
53
62
  data class CategoryConfig(val name: String, val icon: ImageVector)
54
63
 
@@ -0,0 +1,30 @@
1
+ package com.appship.skeleton.data
2
+
3
+ import kotlinx.coroutines.flow.StateFlow
4
+
5
+ /**
6
+ * Abstracts the data layer. Both MockDataProvider and LocalStorageProvider
7
+ * implement this, allowing the app to swap data sources via AppConfig.DataSource.active.
8
+ *
9
+ * Screens and ViewModels depend on this interface rather than on a concrete implementation.
10
+ * Use [DataSourceResolver.repository] to get the active provider.
11
+ */
12
+ interface DataRepository {
13
+ // Observable state
14
+ val items: StateFlow<List<Item>>
15
+ val categories: StateFlow<List<Category>>
16
+ val favoriteItems: StateFlow<List<Item>>
17
+
18
+ // Data loading
19
+ fun loadData()
20
+
21
+ // CRUD operations
22
+ fun addItem(item: Item)
23
+ fun updateItem(item: Item)
24
+ fun deleteItem(item: Item)
25
+ fun toggleFavorite(item: Item)
26
+
27
+ // Queries
28
+ fun searchItems(query: String): List<Item>
29
+ fun itemsByCategory(categoryName: String): List<Item>
30
+ }
@@ -0,0 +1,23 @@
1
+ package com.appship.skeleton.data
2
+
3
+ import com.appship.skeleton.AppConfig
4
+ import com.appship.skeleton.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 items by repository.items.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
+ }
@@ -9,8 +9,8 @@ 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.skeleton.AppConfig
12
+ import com.appship.skeleton.data.DataSourceResolver
12
13
  import com.appship.skeleton.data.Item
13
- import com.appship.skeleton.mock.MockDataProvider
14
14
 
15
15
  // PLACEHOLDER: Customize this screen for creating new items.
16
16
 
@@ -20,6 +20,7 @@ fun CreateScreen(
20
20
  onBack: () -> Unit,
21
21
  modifier: Modifier = Modifier
22
22
  ) {
23
+ val repository = remember { DataSourceResolver.repository }
23
24
  var title by remember { mutableStateOf("") }
24
25
  var subtitle by remember { mutableStateOf("") }
25
26
  var description by remember { mutableStateOf("") }
@@ -47,7 +48,7 @@ fun CreateScreen(
47
48
  description = description,
48
49
  category = selectedCategory
49
50
  )
50
- MockDataProvider.addItem(newItem)
51
+ repository.addItem(newItem)
51
52
  onBack()
52
53
  },
53
54
  enabled = title.isNotBlank(),
@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
13
13
  import androidx.compose.ui.draw.clip
14
14
  import androidx.compose.ui.platform.testTag
15
15
  import androidx.compose.ui.unit.dp
16
- import com.appship.skeleton.mock.MockDataProvider
16
+ import com.appship.skeleton.data.DataSourceResolver
17
17
 
18
18
  // PLACEHOLDER: Customize this screen to show full item details.
19
19
 
@@ -24,7 +24,8 @@ fun DetailScreen(
24
24
  onBack: () -> Unit,
25
25
  modifier: Modifier = Modifier
26
26
  ) {
27
- val items by MockDataProvider.items.collectAsState()
27
+ val repository = remember { DataSourceResolver.repository }
28
+ val items by repository.items.collectAsState()
28
29
  val item = items.firstOrNull { it.id == itemId }
29
30
 
30
31
  Scaffold(
@@ -40,7 +41,7 @@ fun DetailScreen(
40
41
  actions = {
41
42
  if (item != null) {
42
43
  IconButton(
43
- onClick = { MockDataProvider.toggleFavorite(item) },
44
+ onClick = { repository.toggleFavorite(item) },
44
45
  modifier = Modifier.testTag("detail_favorite_button")
45
46
  ) {
46
47
  Icon(