@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.
- package/package.json +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/AppConfig.kt +9 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/data/DataSourceResolver.kt +23 -0
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/booking/BookingScreen.kt +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/ProviderDetailScreen.kt +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/features/discovery/SearchScreen.kt +1 -1
- package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/mock/MockDataProvider.kt +12 -11
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/AppConfig.kt +9 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataRepository.kt +33 -0
- package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt +23 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/AppConfig.kt +9 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataRepository.kt +62 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataSourceResolver.kt +23 -0
- package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt +28 -28
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/AppConfig.kt +9 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataRepository.kt +30 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataSourceResolver.kt +23 -0
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/create/CreateScreen.kt +3 -2
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/detail/DetailScreen.kt +4 -3
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/explore/ExploreScreen.kt +7 -6
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/favorites/FavoritesScreen.kt +4 -3
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/features/home/HomeScreen.kt +5 -4
- package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/mock/MockDataProvider.kt +12 -11
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/AppConfig.kt +9 -0
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataRepository.kt +38 -0
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/data/DataSourceResolver.kt +23 -0
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/feed/FeedScreen.kt +2 -2
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/profile/ProfileScreen.kt +1 -1
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/features/search/SearchScreen.kt +1 -1
- package/templates/android/SocialTemplate/app/src/main/kotlin/com/appship/social/mock/MockDataProvider.kt +7 -6
- package/templates/ios/BookTemplate/BookTemplate/App/AppConfig.swift +12 -2
- package/templates/ios/BookTemplate/BookTemplate/Data/DataRepository.swift +33 -0
- package/templates/ios/BookTemplate/BookTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/BookTemplate/BookTemplate/Features/Booking/BookingView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/DiscoveryView.swift +3 -3
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/MapExploreView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/ProviderDetailView.swift +1 -1
- package/templates/ios/BookTemplate/BookTemplate/Features/Discovery/SearchView.swift +2 -2
- package/templates/ios/BookTemplate/BookTemplate/MockData/MockDataProvider.swift +18 -1
- package/templates/ios/MapTemplate/MapTemplate/App/AppConfig.swift +11 -9
- package/templates/ios/MapTemplate/MapTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/MapTemplate/MapTemplate/Features/Favorites/FavoritesView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/PlaceDetail/PlaceDetailView.swift +2 -2
- package/templates/ios/MapTemplate/MapTemplate/Features/Profile/ProfileView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/Route/RouteView.swift +1 -1
- package/templates/ios/MapTemplate/MapTemplate/Features/Search/SearchView.swift +1 -1
- package/templates/ios/ShopTemplate/ShopTemplate/App/AppConfig.swift +9 -0
- package/templates/ios/ShopTemplate/ShopTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Home/HomeView.swift +9 -7
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Orders/OrdersView.swift +7 -3
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductDetailView.swift +4 -2
- package/templates/ios/ShopTemplate/ShopTemplate/Features/Products/ProductListView.swift +6 -3
- package/templates/ios/ShopTemplate/ShopTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/Skeleton/Skeleton/App/AppConfig.swift +9 -0
- package/templates/ios/Skeleton/Skeleton/Data/DataRepository.swift +30 -0
- package/templates/ios/Skeleton/Skeleton/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/Skeleton/Skeleton/Features/Explore/ExploreViewModel.swift +7 -6
- package/templates/ios/Skeleton/Skeleton/Features/Home/HomeViewModel.swift +8 -7
- package/templates/ios/Skeleton/Skeleton/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/App/AppConfig.swift +4 -3
- package/templates/ios/SocialTemplate/SocialTemplate/Data/DataRepository.swift +37 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Data/DataSourceResolver.swift +20 -0
- package/templates/ios/SocialTemplate/SocialTemplate/Data/SyncManager.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/CreatePost/CreatePostView.swift +2 -2
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/CommentsView.swift +2 -2
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Feed/FeedView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Messages/MessagesView.swift +4 -4
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Notifications/NotificationsView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Profile/ProfileView.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Features/Search/SearchView.swift +5 -4
- package/templates/ios/SocialTemplate/SocialTemplate/MockData/MockDataProvider.swift +1 -1
- package/templates/ios/SocialTemplate/SocialTemplate/Models/Models.swift +2 -2
package/package.json
CHANGED
|
@@ -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
|
package/templates/android/BookTemplate/app/src/main/kotlin/com/appship/book/mock/MockDataProvider.kt
CHANGED
|
@@ -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
|
}
|
package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataRepository.kt
ADDED
|
@@ -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
|
+
}
|
package/templates/android/MapTemplate/app/src/main/kotlin/com/appship/map/data/DataSourceResolver.kt
ADDED
|
@@ -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
|
}
|
package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/data/DataRepository.kt
ADDED
|
@@ -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
|
+
}
|
package/templates/android/ShopTemplate/app/src/main/kotlin/com/appship/shop/mock/MockDataProvider.kt
CHANGED
|
@@ -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
|
|
package/templates/android/Skeleton/app/src/main/kotlin/com/appship/skeleton/data/DataRepository.kt
ADDED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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 = {
|
|
44
|
+
onClick = { repository.toggleFavorite(item) },
|
|
44
45
|
modifier = Modifier.testTag("detail_favorite_button")
|
|
45
46
|
) {
|
|
46
47
|
Icon(
|