@shaykec/app-agent 1.0.2 → 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 (77) hide show
  1. package/dist/engines/claude-engine.d.ts.map +1 -1
  2. package/dist/engines/claude-engine.js +1 -5
  3. package/dist/engines/claude-engine.js.map +1 -1
  4. package/dist/workspace.js +30 -4
  5. package/dist/workspace.js.map +1 -1
  6. package/package.json +1 -1
  7. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/AppConfig.kt +9 -0
  8. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/data/DataSourceResolver.kt +23 -0
  9. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +1 -1
  10. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/ProviderDetailScreen.kt +1 -1
  11. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/SearchScreen.kt +1 -1
  12. package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/mock/MockDataProvider.kt +12 -11
  13. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/AppConfig.kt +9 -0
  14. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataRepository.kt +33 -0
  15. package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +23 -0
  16. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/AppConfig.kt +9 -0
  17. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataRepository.kt +62 -0
  18. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataSourceResolver.kt +23 -0
  19. package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt +28 -28
  20. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/AppConfig.kt +9 -0
  21. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataRepository.kt +30 -0
  22. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataSourceResolver.kt +23 -0
  23. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/create/CreateScreen.kt +3 -2
  24. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/detail/DetailScreen.kt +4 -3
  25. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/explore/ExploreScreen.kt +7 -6
  26. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/favorites/FavoritesScreen.kt +4 -3
  27. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/home/HomeScreen.kt +5 -4
  28. package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/mock/MockDataProvider.kt +12 -11
  29. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/AppConfig.kt +9 -0
  30. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +38 -0
  31. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataSourceResolver.kt +23 -0
  32. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/FeedScreen.kt +2 -2
  33. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/profile/ProfileScreen.kt +1 -1
  34. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +1 -1
  35. package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/mock/MockDataProvider.kt +7 -6
  36. package/templates/ios/BookTemplate/BookTemplate/App/AppConfig.swift +12 -2
  37. package/templates/ios/BookTemplate/BookTemplate/Data/DataRepository.swift +33 -0
  38. package/templates/ios/BookTemplate/BookTemplate/Data/DataSourceResolver.swift +20 -0
  39. package/templates/ios/BookTemplate/BookTemplate/Features/Booking/BookingView.swift +1 -1
  40. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/DiscoveryView.swift +3 -3
  41. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/MapExploreView.swift +1 -1
  42. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/ProviderDetailView.swift +1 -1
  43. package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/SearchView.swift +2 -2
  44. package/templates/ios/BookTemplate/BookTemplate/MockData/MockDataProvider.swift +18 -1
  45. package/templates/ios/MapTemplate/MapTemplate/App/AppConfig.swift +11 -9
  46. package/templates/ios/MapTemplate/MapTemplate/Data/DataSourceResolver.swift +20 -0
  47. package/templates/ios/MapTemplate/MapTemplate/Features/Favorites/FavoritesView.swift +1 -1
  48. package/templates/ios/MapTemplate/MapTemplate/Features/PlaceDetail/PlaceDetailView.swift +2 -2
  49. package/templates/ios/MapTemplate/MapTemplate/Features/Profile/ProfileView.swift +1 -1
  50. package/templates/ios/MapTemplate/MapTemplate/Features/Route/RouteView.swift +1 -1
  51. package/templates/ios/MapTemplate/MapTemplate/Features/Search/SearchView.swift +1 -1
  52. package/templates/ios/ShopTemplate/ShopTemplate/App/AppConfig.swift +9 -0
  53. package/templates/ios/ShopTemplate/ShopTemplate/Data/DataSourceResolver.swift +20 -0
  54. package/templates/ios/ShopTemplate/ShopTemplate/Features/Home/HomeView.swift +9 -7
  55. package/templates/ios/ShopTemplate/ShopTemplate/Features/Orders/OrdersView.swift +7 -3
  56. package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductDetailView.swift +4 -2
  57. package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductListView.swift +6 -3
  58. package/templates/ios/ShopTemplate/ShopTemplate/MockData/MockDataProvider.swift +1 -1
  59. package/templates/ios/Skeleton/Skeleton/App/AppConfig.swift +9 -0
  60. package/templates/ios/Skeleton/Skeleton/Data/DataRepository.swift +30 -0
  61. package/templates/ios/Skeleton/Skeleton/Data/DataSourceResolver.swift +20 -0
  62. package/templates/ios/Skeleton/Skeleton/Features/Explore/ExploreViewModel.swift +7 -6
  63. package/templates/ios/Skeleton/Skeleton/Features/Home/HomeViewModel.swift +8 -7
  64. package/templates/ios/Skeleton/Skeleton/MockData/MockDataProvider.swift +1 -1
  65. package/templates/ios/SocialTemplate/SocialTemplate/App/AppConfig.swift +4 -3
  66. package/templates/ios/SocialTemplate/SocialTemplate/Data/DataRepository.swift +37 -0
  67. package/templates/ios/SocialTemplate/SocialTemplate/Data/DataSourceResolver.swift +20 -0
  68. package/templates/ios/SocialTemplate/SocialTemplate/Data/SyncManager.swift +1 -1
  69. package/templates/ios/SocialTemplate/SocialTemplate/Features/CreatePost/CreatePostView.swift +2 -2
  70. package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/CommentsView.swift +2 -2
  71. package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/FeedView.swift +1 -1
  72. package/templates/ios/SocialTemplate/SocialTemplate/Features/Messages/MessagesView.swift +4 -4
  73. package/templates/ios/SocialTemplate/SocialTemplate/Features/Notifications/NotificationsView.swift +1 -1
  74. package/templates/ios/SocialTemplate/SocialTemplate/Features/Profile/ProfileView.swift +1 -1
  75. package/templates/ios/SocialTemplate/SocialTemplate/Features/Search/SearchView.swift +5 -4
  76. package/templates/ios/SocialTemplate/SocialTemplate/MockData/MockDataProvider.swift +1 -1
  77. package/templates/ios/SocialTemplate/SocialTemplate/Models/Models.swift +2 -2
@@ -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(
@@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
12
12
  import androidx.compose.ui.platform.testTag
13
13
  import androidx.compose.ui.unit.dp
14
14
  import com.appship.skeleton.AppConfig
15
- import com.appship.skeleton.mock.MockDataProvider
15
+ import com.appship.skeleton.data.DataSourceResolver
16
16
  import com.appship.skeleton.features.home.ItemCard
17
17
 
18
18
  // PLACEHOLDER: Customize this screen for search/browse functionality.
@@ -23,10 +23,11 @@ fun ExploreScreen(
23
23
  onItemClick: (String) -> Unit,
24
24
  modifier: Modifier = Modifier
25
25
  ) {
26
+ val repository = remember { DataSourceResolver.repository }
26
27
  var searchQuery by remember { mutableStateOf("") }
27
28
  var isActive by remember { mutableStateOf(false) }
28
- val categories by MockDataProvider.categories.collectAsState()
29
- val searchResults = remember(searchQuery) { MockDataProvider.searchItems(searchQuery) }
29
+ val categories by repository.categories.collectAsState()
30
+ val searchResults = remember(searchQuery) { repository.searchItems(searchQuery) }
30
31
 
31
32
  Scaffold(
32
33
  topBar = {
@@ -79,7 +80,7 @@ fun ExploreScreen(
79
80
  ItemCard(
80
81
  item = item,
81
82
  onClick = { onItemClick(item.id) },
82
- onFavorite = { MockDataProvider.toggleFavorite(item) },
83
+ onFavorite = { repository.toggleFavorite(item) },
83
84
  modifier = Modifier.testTag("explore_result_${index}_card")
84
85
  )
85
86
  }
@@ -91,7 +92,7 @@ fun ExploreScreen(
91
92
  verticalArrangement = Arrangement.spacedBy(16.dp)
92
93
  ) {
93
94
  itemsIndexed(categories) { index, category ->
94
- val categoryItems = MockDataProvider.itemsByCategory(category.name)
95
+ val categoryItems = repository.itemsByCategory(category.name)
95
96
  Column(modifier = Modifier.testTag("explore_category_${index}")) {
96
97
  Text(
97
98
  text = "${category.name} (${categoryItems.size})",
@@ -102,7 +103,7 @@ fun ExploreScreen(
102
103
  ItemCard(
103
104
  item = item,
104
105
  onClick = { onItemClick(item.id) },
105
- onFavorite = { MockDataProvider.toggleFavorite(item) },
106
+ onFavorite = { repository.toggleFavorite(item) },
106
107
  modifier = Modifier.padding(bottom = 8.dp)
107
108
  )
108
109
  }
@@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
12
12
  import androidx.compose.ui.platform.testTag
13
13
  import androidx.compose.ui.unit.dp
14
14
  import com.appship.skeleton.AppConfig
15
- import com.appship.skeleton.mock.MockDataProvider
15
+ import com.appship.skeleton.data.DataSourceResolver
16
16
  import com.appship.skeleton.features.home.ItemCard
17
17
 
18
18
  // PLACEHOLDER: Customize this screen to show saved/favorited items.
@@ -23,7 +23,8 @@ fun FavoritesScreen(
23
23
  onItemClick: (String) -> Unit,
24
24
  modifier: Modifier = Modifier
25
25
  ) {
26
- val favoriteItems by MockDataProvider.favoriteItems.collectAsState()
26
+ val repository = remember { DataSourceResolver.repository }
27
+ val favoriteItems by repository.favoriteItems.collectAsState()
27
28
 
28
29
  Scaffold(
29
30
  topBar = {
@@ -64,7 +65,7 @@ fun FavoritesScreen(
64
65
  ItemCard(
65
66
  item = item,
66
67
  onClick = { onItemClick(item.id) },
67
- onFavorite = { MockDataProvider.toggleFavorite(item) },
68
+ onFavorite = { repository.toggleFavorite(item) },
68
69
  modifier = Modifier.testTag("favorites_item_${index}_card")
69
70
  )
70
71
  }
@@ -16,8 +16,8 @@ import androidx.compose.ui.draw.clip
16
16
  import androidx.compose.ui.platform.testTag
17
17
  import androidx.compose.ui.unit.dp
18
18
  import com.appship.skeleton.AppConfig
19
+ import com.appship.skeleton.data.DataSourceResolver
19
20
  import com.appship.skeleton.data.Item
20
- import com.appship.skeleton.mock.MockDataProvider
21
21
 
22
22
  // PLACEHOLDER: Customize this screen for your app's main content.
23
23
 
@@ -28,8 +28,9 @@ fun HomeScreen(
28
28
  onCreateClick: () -> Unit,
29
29
  modifier: Modifier = Modifier
30
30
  ) {
31
- val items by MockDataProvider.items.collectAsState()
32
- val categories by MockDataProvider.categories.collectAsState()
31
+ val repository = remember { DataSourceResolver.repository }
32
+ val items by repository.items.collectAsState()
33
+ val categories by repository.categories.collectAsState()
33
34
  var selectedCategory by remember { mutableStateOf<String?>(null) }
34
35
 
35
36
  val filteredItems = if (selectedCategory != null) {
@@ -82,7 +83,7 @@ fun HomeScreen(
82
83
  ItemCard(
83
84
  item = item,
84
85
  onClick = { onItemClick(item.id) },
85
- onFavorite = { MockDataProvider.toggleFavorite(item) },
86
+ onFavorite = { repository.toggleFavorite(item) },
86
87
  modifier = Modifier.testTag("home_item_${index}_card")
87
88
  )
88
89
  }
@@ -2,6 +2,7 @@ package com.appship.skeleton.mock
2
2
 
3
3
  import com.appship.skeleton.AppConfig
4
4
  import com.appship.skeleton.data.Category
5
+ import com.appship.skeleton.data.DataRepository
5
6
  import com.appship.skeleton.data.Item
6
7
  import kotlinx.coroutines.flow.MutableStateFlow
7
8
  import kotlinx.coroutines.flow.StateFlow
@@ -11,21 +12,21 @@ import java.util.Date
11
12
  // PLACEHOLDER: Replace sample data with domain-specific content.
12
13
  // Keep the singleton pattern and StateFlow interface.
13
14
 
14
- object MockDataProvider {
15
+ object MockDataProvider : DataRepository {
15
16
  private val _items = MutableStateFlow<List<Item>>(emptyList())
16
- val items: StateFlow<List<Item>> = _items.asStateFlow()
17
+ override val items: StateFlow<List<Item>> = _items.asStateFlow()
17
18
 
18
19
  private val _categories = MutableStateFlow<List<Category>>(emptyList())
19
- val categories: StateFlow<List<Category>> = _categories.asStateFlow()
20
+ override val categories: StateFlow<List<Category>> = _categories.asStateFlow()
20
21
 
21
22
  private val _favoriteItems = MutableStateFlow<List<Item>>(emptyList())
22
- val favoriteItems: StateFlow<List<Item>> = _favoriteItems.asStateFlow()
23
+ override val favoriteItems: StateFlow<List<Item>> = _favoriteItems.asStateFlow()
23
24
 
24
25
  init {
25
26
  loadData()
26
27
  }
27
28
 
28
- fun loadData() {
29
+ override fun loadData() {
29
30
  _categories.value = generateCategories()
30
31
  _items.value = generateItems()
31
32
  _favoriteItems.value = _items.value.filter { it.isFavorite }
@@ -33,30 +34,30 @@ object MockDataProvider {
33
34
 
34
35
  // MARK: - CRUD Operations
35
36
 
36
- fun addItem(item: Item) {
37
+ override fun addItem(item: Item) {
37
38
  _items.value = listOf(item) + _items.value
38
39
  if (item.isFavorite) {
39
40
  _favoriteItems.value = listOf(item) + _favoriteItems.value
40
41
  }
41
42
  }
42
43
 
43
- fun updateItem(item: Item) {
44
+ override fun updateItem(item: Item) {
44
45
  _items.value = _items.value.map { if (it.id == item.id) item else it }
45
46
  _favoriteItems.value = _items.value.filter { it.isFavorite }
46
47
  }
47
48
 
48
- fun deleteItem(item: Item) {
49
+ override fun deleteItem(item: Item) {
49
50
  _items.value = _items.value.filter { it.id != item.id }
50
51
  _favoriteItems.value = _favoriteItems.value.filter { it.id != item.id }
51
52
  }
52
53
 
53
- fun toggleFavorite(item: Item) {
54
+ override fun toggleFavorite(item: Item) {
54
55
  val updated = item.copy(isFavorite = !item.isFavorite)
55
56
  _items.value = _items.value.map { if (it.id == item.id) updated else it }
56
57
  _favoriteItems.value = _items.value.filter { it.isFavorite }
57
58
  }
58
59
 
59
- fun searchItems(query: String): List<Item> {
60
+ override fun searchItems(query: String): List<Item> {
60
61
  if (query.isBlank()) return _items.value
61
62
  val lower = query.lowercase()
62
63
  return _items.value.filter {
@@ -66,7 +67,7 @@ object MockDataProvider {
66
67
  }
67
68
  }
68
69
 
69
- fun itemsByCategory(categoryName: String): List<Item> {
70
+ override fun itemsByCategory(categoryName: String): List<Item> {
70
71
  return _items.value.filter { it.category == categoryName }
71
72
  }
72
73
 
@@ -61,4 +61,13 @@ object AppConfig {
61
61
  OnboardingSlide("Discover", "Explore content from creators worldwide", "search"),
62
62
  OnboardingSlide("Create", "Express yourself through posts and stories", "add_circle")
63
63
  )
64
+
65
+ // CUSTOMIZE:DATASOURCE - Data source configuration
66
+ object DataSource {
67
+ enum class SourceType {
68
+ LOCAL_STORAGE, // Room — real local database (DEFAULT)
69
+ MOCK // In-memory seed data — for development/testing only
70
+ }
71
+ val active: SourceType = SourceType.MOCK
72
+ }
64
73
  }
@@ -0,0 +1,38 @@
1
+ package com.appship.social.data
2
+
3
+ import com.appship.social.data.*
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 currentUser: User
16
+ val users: List<User>
17
+ val posts: StateFlow<List<Post>>
18
+ val conversations: StateFlow<List<Conversation>>
19
+ val notifications: StateFlow<List<AppNotification>>
20
+ val stories: List<Story>
21
+ val trendingHashtags: List<Hashtag>
22
+
23
+ // Helper functions
24
+ fun getComments(postId: String): List<Comment>
25
+
26
+ // CRUD operations
27
+ fun createPost(content: String, mediaUrls: List<String> = emptyList(), mediaType: MediaType = MediaType.NONE, location: String? = null, tags: List<String>? = null): Post
28
+ fun updatePost(post: Post)
29
+ fun deletePost(postId: String)
30
+ fun likePost(postId: String)
31
+ fun unlikePost(postId: String)
32
+ fun followUser(userId: String)
33
+ fun unfollowUser(userId: String)
34
+ fun sendMessage(conversationId: String, content: String, mediaUrl: String? = null, mediaType: MediaType = MediaType.NONE): Message
35
+ fun markNotificationRead(notificationId: String)
36
+ fun markAllNotificationsRead()
37
+ fun reset()
38
+ }
@@ -0,0 +1,23 @@
1
+ package com.appship.social.data
2
+
3
+ import com.appship.social.AppConfig
4
+ import com.appship.social.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 posts by repository.posts.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
+ }
@@ -34,7 +34,7 @@ import com.appship.social.core.theme.StoryRingMiddle
34
34
  import com.appship.social.core.theme.StoryRingStart
35
35
  import com.appship.social.data.Post
36
36
  import com.appship.social.data.Story
37
- import com.appship.social.mock.MockDataProvider
37
+ import com.appship.social.data.DataSourceResolver
38
38
 
39
39
  @OptIn(ExperimentalMaterial3Api::class)
40
40
  @Composable
@@ -129,7 +129,7 @@ fun StoriesRow(
129
129
  ) {
130
130
  Box {
131
131
  AsyncImage(
132
- model = MockDataProvider.currentUser.avatarUrl,
132
+ model = repository.currentUser.avatarUrl,
133
133
  contentDescription = null,
134
134
  modifier = Modifier
135
135
  .size(64.dp)
@@ -25,7 +25,7 @@ import coil.compose.AsyncImage
25
25
  import com.appship.social.AppConfig
26
26
  import com.appship.social.data.Post
27
27
  import com.appship.social.data.User
28
- import com.appship.social.mock.MockDataProvider
28
+ import com.appship.social.data.DataSourceResolver
29
29
 
30
30
  @OptIn(ExperimentalMaterial3Api::class)
31
31
  @Composable
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
25
25
  import coil.compose.AsyncImage
26
26
  import com.appship.social.AppConfig
27
27
  import com.appship.social.data.User
28
- import com.appship.social.mock.MockDataProvider
28
+ import com.appship.social.data.DataSourceResolver
29
29
 
30
30
  @OptIn(ExperimentalMaterial3Api::class)
31
31
  @Composable
@@ -1,6 +1,7 @@
1
1
  package com.appship.social.mock
2
2
 
3
3
  import com.appship.social.data.*
4
+ import com.appship.social.data.DataRepository
4
5
  import com.appship.social.data.SyncManager
5
6
  import java.time.LocalDateTime
6
7
  import java.util.UUID
@@ -8,7 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
8
9
  import kotlinx.coroutines.flow.StateFlow
9
10
  import kotlinx.coroutines.flow.asStateFlow
10
11
 
11
- object MockDataProvider {
12
+ object MockDataProvider : DataRepository {
12
13
 
13
14
  val currentUser = User(
14
15
  id = "current",
@@ -255,7 +256,7 @@ object MockDataProvider {
255
256
  }
256
257
 
257
258
  // Messages
258
- fun sendMessage(conversationId: String, content: String, mediaUrl: String? = null, mediaType: MediaType = MediaType.NONE): Message {
259
+ override fun sendMessage(conversationId: String, content: String, mediaUrl: String? = null, mediaType: MediaType = MediaType.NONE): Message {
259
260
  val newMessage = Message(
260
261
  id = UUID.randomUUID().toString(),
261
262
  senderId = currentUser.id,
@@ -280,7 +281,7 @@ object MockDataProvider {
280
281
  return newMessage
281
282
  }
282
283
 
283
- fun markNotificationRead(notificationId: String) {
284
+ override fun markNotificationRead(notificationId: String) {
284
285
  _notifications.value = _notifications.value.map { notif ->
285
286
  if (notif.id == notificationId && !notif.isRead) {
286
287
  SyncManager.addPendingNotification(notificationId)
@@ -289,7 +290,7 @@ object MockDataProvider {
289
290
  }
290
291
  }
291
292
 
292
- fun markAllNotificationsRead() {
293
+ override fun markAllNotificationsRead() {
293
294
  _notifications.value = _notifications.value.map { notif ->
294
295
  if (!notif.isRead) {
295
296
  SyncManager.addPendingNotification(notif.id)
@@ -299,7 +300,7 @@ object MockDataProvider {
299
300
  }
300
301
 
301
302
  // MARK: - Reset
302
- fun reset() {
303
+ override fun reset() {
303
304
  _posts.value = defaultPosts
304
305
  _conversations.value = defaultConversations
305
306
  _notifications.value = defaultNotifications
@@ -307,7 +308,7 @@ object MockDataProvider {
307
308
  }
308
309
 
309
310
  // MARK: - Helper Functions
310
- fun getComments(postId: String): List<Comment> {
311
+ override fun getComments(postId: String): List<Comment> {
311
312
  return (1..5).map { i ->
312
313
  Comment(
313
314
  id = "c$postId-$i",