├── .gitattributes ├── composeApp ├── src │ ├── androidMain │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── santimattius │ │ │ │ └── kmp │ │ │ │ ├── delivery │ │ │ │ └── features │ │ │ │ │ ├── home │ │ │ │ │ └── HomeScreen.android.kt │ │ │ │ │ └── map │ │ │ │ │ └── MapScreen.android.kt │ │ │ │ └── android │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── commonMain │ │ ├── composeResources │ │ │ ├── values │ │ │ │ └── values.xml │ │ │ ├── drawable │ │ │ │ └── compose-multiplatform.xml │ │ │ └── files │ │ │ │ └── vendors.json │ │ └── kotlin │ │ │ ├── com │ │ │ └── santimattius │ │ │ │ └── kmp │ │ │ │ └── delivery │ │ │ │ ├── di │ │ │ │ ├── AppQualifiers.kt │ │ │ │ └── Dependencies.kt │ │ │ │ ├── core │ │ │ │ ├── data │ │ │ │ │ ├── sources │ │ │ │ │ │ ├── VendorRemoteDataSource.kt │ │ │ │ │ │ ├── VendorLocalDataSource.kt │ │ │ │ │ │ ├── RequestBuilder.kt │ │ │ │ │ │ ├── local │ │ │ │ │ │ │ └── InMemoryVendorLocalDataSource.kt │ │ │ │ │ │ ├── VendorMockRemoteDataSource.kt │ │ │ │ │ │ └── ktor │ │ │ │ │ │ │ └── KtorVendorRemoteDataSource.kt │ │ │ │ │ ├── VendorsResponse.kt │ │ │ │ │ ├── ModelMapping.kt │ │ │ │ │ ├── VendorsBody.kt │ │ │ │ │ └── VendorsRepository.kt │ │ │ │ ├── ui │ │ │ │ │ ├── themes │ │ │ │ │ │ ├── Color.kt │ │ │ │ │ │ ├── Type.kt │ │ │ │ │ │ └── Theme.kt │ │ │ │ │ └── components │ │ │ │ │ │ ├── Center.kt │ │ │ │ │ │ ├── AppBar.kt │ │ │ │ │ │ ├── LottieLoader.kt │ │ │ │ │ │ ├── NetworkImage.kt │ │ │ │ │ │ ├── CircularAvatar.kt │ │ │ │ │ │ └── SearchAppBar.kt │ │ │ │ ├── domain │ │ │ │ │ └── Vendor.kt │ │ │ │ └── network │ │ │ │ │ └── Client.kt │ │ │ │ ├── features │ │ │ │ ├── splash │ │ │ │ │ └── SplashScreen.kt │ │ │ │ ├── map │ │ │ │ │ ├── MapViewModel.kt │ │ │ │ │ └── MapScreen.kt │ │ │ │ └── home │ │ │ │ │ ├── HomeScreenViewModel.kt │ │ │ │ │ └── HomeScreen.kt │ │ │ │ └── MainApplication.kt │ │ │ └── App.kt │ └── iosMain │ │ └── kotlin │ │ ├── NativeViewFactory.kt │ │ ├── MainViewController.kt │ │ └── com │ │ └── santimattius │ │ └── kmp │ │ └── delivery │ │ └── features │ │ ├── map │ │ └── MapScreen.ios.kt │ │ └── home │ │ └── HomeScreen.ios.kt └── build.gradle.kts ├── iosApp ├── iosApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── app-icon-1024.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── iOSApp.swift │ ├── ContentView.swift │ ├── NativeMapView.swift │ ├── Info.plist │ └── NativeVendorRow.swift ├── Configuration │ └── Config.xcconfig └── iosApp.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── gradle.properties ├── .gitignore ├── .fleet └── receipt.json ├── settings.gradle.kts ├── README.md ├── gradlew.bat └── gradlew /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Delivery 3 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | Delivery 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.santimattius.kmp.compose.skeleton.kmp-compose-gradle-skeleton 3 | APP_NAME=kmp-compose-gradle-skeleton -------------------------------------------------------------------------------- /iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santimattius/cmp-delivery-application/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/di/AppQualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.di 2 | 3 | enum class AppQualifiers { 4 | Client, 5 | BaseUrl, 6 | FileSource, 7 | RemoteSource 8 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/VendorRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources 2 | 3 | import com.santimattius.kmp.delivery.core.domain.VendorResult 4 | 5 | interface VendorRemoteDataSource { 6 | 7 | suspend fun getVendors(): Result 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/santimattius/kmp/delivery/features/home/HomeScreen.android.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.home 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.santimattius.kmp.delivery.core.domain.Vendor 5 | 6 | @Composable 7 | actual fun NativeVendorItem(vendor: Vendor) { 8 | VendorRowItem(vendor = vendor) 9 | } -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/NativeViewFactory.kt: -------------------------------------------------------------------------------- 1 | import com.santimattius.kmp.delivery.core.domain.Vendor 2 | import platform.UIKit.UIViewController 3 | 4 | interface NativeViewFactory { 5 | 6 | fun createMapView( 7 | vendors: List, 8 | onItemClick: (Vendor) -> Unit 9 | ): UIViewController 10 | 11 | fun createVendorRow(vendor: Vendor): UIViewController 12 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | #Gradle 4 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 5 | 6 | 7 | #Android 8 | android.nonTransitiveRClass=true 9 | android.useAndroidX=true 10 | 11 | #MPP 12 | kotlin.mpp.androidSourceSetLayoutVersion=2 13 | kotlin.mpp.enableCInteropCommonization=true 14 | 15 | #Development 16 | development=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | **/build/ 4 | xcuserdata 5 | !src/**/build/ 6 | local.properties 7 | .idea 8 | .DS_Store 9 | captures 10 | .externalNativeBuild 11 | .cxx 12 | *.xcodeproj/* 13 | !*.xcodeproj/project.pbxproj 14 | !*.xcodeproj/xcshareddata/ 15 | !*.xcodeproj/project.xcworkspace/ 16 | !*.xcworkspace/contents.xcworkspacedata 17 | **/xcshareddata/WorkspaceSettings.xcsettings 18 | /.kotlin/ 19 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/themes/Color.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.entertainment.core.ui.themes 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /.fleet/receipt.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec": { 3 | "template_id": "kmt", 4 | "targets": { 5 | "android": { 6 | "ui": [ 7 | "compose" 8 | ] 9 | }, 10 | "ios": { 11 | "ui": [ 12 | "compose" 13 | ] 14 | } 15 | } 16 | }, 17 | "timestamp": "2023-12-12T11:51:15.531908775Z" 18 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/VendorLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources 2 | 3 | import com.santimattius.kmp.delivery.core.domain.Vendor 4 | import com.santimattius.kmp.delivery.core.domain.VendorResult 5 | 6 | interface VendorLocalDataSource { 7 | 8 | suspend fun getVendors(): Result 9 | 10 | suspend fun save(vendors: List) 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "cmp-delivery-application" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 7 | google() 8 | gradlePluginPortal() 9 | mavenCentral() 10 | } 11 | } 12 | 13 | dependencyResolutionManagement { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 18 | } 19 | } 20 | 21 | include(":composeApp") 22 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/MainViewController.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.CompositionLocalProvider 2 | import androidx.compose.runtime.staticCompositionLocalOf 3 | import androidx.compose.ui.window.ComposeUIViewController 4 | 5 | val LocalNativeViewFactory = staticCompositionLocalOf { 6 | error("LocalNativeViewFactory not provided") 7 | } 8 | 9 | fun MainViewController( 10 | nativeViewFactory: NativeViewFactory 11 | ) = ComposeUIViewController { 12 | CompositionLocalProvider(LocalNativeViewFactory provides nativeViewFactory) { 13 | App() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/themes/Type.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.entertainment.core.ui.themes 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | val Typography = Typography( 10 | bodyLarge = TextStyle( 11 | fontFamily = FontFamily.Default, 12 | fontWeight = FontWeight.Normal, 13 | fontSize = 16.sp, 14 | lineHeight = 24.sp, 15 | letterSpacing = 0.5.sp 16 | ) 17 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import coil3.ImageLoader 3 | import coil3.compose.setSingletonImageLoaderFactory 4 | import coil3.network.ktor3.KtorNetworkFetcherFactory 5 | import com.santimattius.kmp.delivery.MainApplication 6 | import com.santimattius.kmp.delivery.core.ui.themes.AppTheme 7 | 8 | @Composable 9 | fun App() { 10 | setSingletonImageLoaderFactory { context -> 11 | ImageLoader.Builder(context) 12 | .components { 13 | add(KtorNetworkFetcherFactory()) 14 | } 15 | .build() 16 | } 17 | AppTheme { 18 | MainApplication() 19 | } 20 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/RequestBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources 2 | 3 | import com.santimattius.kmp.delivery.core.data.Offer 4 | import com.santimattius.kmp.delivery.core.data.Point 5 | import com.santimattius.kmp.delivery.core.data.VendorsBody 6 | 7 | object RequestBuilder { 8 | 9 | fun makeDefaultBody(): VendorsBody { 10 | return VendorsBody( 11 | businessTypes = listOf("RESTAURANT"), 12 | countryID = 1, 13 | point = Point(-34.90111, -56.16453), 14 | sort = "RANKING", 15 | offer = Offer(occasions = listOf("DELIVERY", "PICKUP")) 16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/VendorsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class VendorDto( 7 | val id: Long, 8 | val name: String, 9 | val logo: String, 10 | val location: Location, 11 | val isNew: Boolean, 12 | val isExclusive: Boolean, 13 | val deliveryTime: String, 14 | val deliveryFee: Double, 15 | val rating: Double, 16 | val headerImage: String, 17 | val categories: List 18 | ) { 19 | val isFavorite: Boolean = false 20 | } 21 | 22 | @Serializable 23 | data class Location( 24 | val lat: Double, 25 | val lng: Double 26 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/domain/Vendor.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.domain 2 | 3 | data class Vendor( 4 | val id: Long, 5 | val name: String, 6 | val logo: String, 7 | val location: Location, 8 | val isNew: Boolean, 9 | val isExclusive: Boolean, 10 | val deliveryTime: String, 11 | val deliveryFee: Double, 12 | val rating: Double, 13 | val headerImage: String, 14 | val categories: List 15 | ) { 16 | val isFavorite: Boolean = false 17 | } 18 | 19 | data class Location( 20 | val lat: Double, 21 | val lng: Double 22 | ) 23 | 24 | data class VendorResult( 25 | val total: Long, 26 | val vendors: List 27 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/ModelMapping.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data 2 | 3 | import com.santimattius.kmp.delivery.core.data.VendorDto 4 | import com.santimattius.kmp.delivery.core.domain.Location 5 | import com.santimattius.kmp.delivery.core.domain.Vendor 6 | 7 | fun List.asDomains(): List { 8 | return map { it.asDomain() } 9 | } 10 | 11 | private fun VendorDto.asDomain(): Vendor { 12 | return Vendor( 13 | id = id, 14 | name = name, 15 | logo = logo, 16 | deliveryTime = deliveryTime, 17 | deliveryFee = deliveryFee, 18 | location = Location(location.lat, location.lng), 19 | rating = rating, 20 | isExclusive = isExclusive, 21 | isNew = isNew, 22 | headerImage = headerImage, 23 | categories = categories, 24 | ) 25 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/VendorsBody.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data 2 | 3 | // To parse the JSON, install kotlin's serialization plugin and do: 4 | // 5 | // val json = Json { allowStructuredMapKeys = true } 6 | // val vendorsBody = json.parse(VendorsBody.serializer(), jsonString) 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | @Serializable 10 | data class VendorsBody ( 11 | val filters: List = emptyList(), 12 | val businessTypes: List, 13 | @SerialName("countryId") 14 | val countryID: Long, 15 | val point: Point, 16 | val sort: String, 17 | val offer: Offer 18 | ) 19 | 20 | @Serializable 21 | data class Offer ( 22 | val occasions: List 23 | ) 24 | 25 | @Serializable 26 | data class Point ( 27 | val latitude: Double, 28 | val longitude: Double 29 | ) 30 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/Center.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.CircularProgressIndicator 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | 11 | @Composable 12 | private fun Center(content: @Composable () -> Unit) { 13 | Box( 14 | modifier = Modifier.fillMaxSize(), 15 | contentAlignment = Alignment.Center 16 | ) { 17 | content() 18 | } 19 | } 20 | 21 | @Composable 22 | fun LoadingIndicator() { 23 | Center { 24 | CircularProgressIndicator() 25 | } 26 | } 27 | 28 | @Composable 29 | fun ErrorView(message: String) { 30 | Center { Text(message) } 31 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/santimattius/kmp/delivery/features/map/MapScreen.android.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.map 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import com.santimattius.kmp.delivery.core.domain.Vendor 11 | import com.santimattius.kmp.delivery.core.ui.components.LottieLoader 12 | 13 | @Composable 14 | actual fun MapView( 15 | vendors: List, 16 | onItemClick: (Vendor) -> Unit, 17 | modifier: Modifier, 18 | ) { 19 | Box( 20 | modifier = modifier.fillMaxSize(), 21 | contentAlignment = Alignment.Center 22 | ) { 23 | LottieLoader( 24 | resource = "files/avocado.json", 25 | contentDescription = "avocado" 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/local/InMemoryVendorLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources.local 2 | 3 | import com.santimattius.kmp.delivery.core.data.sources.VendorLocalDataSource 4 | import com.santimattius.kmp.delivery.core.domain.Vendor 5 | import com.santimattius.kmp.delivery.core.domain.VendorResult 6 | import kotlinx.coroutines.sync.Mutex 7 | import kotlinx.coroutines.sync.withLock 8 | 9 | class InMemoryVendorLocalDataSource : VendorLocalDataSource { 10 | private val mutex = Mutex() 11 | private val vendors = mutableListOf() 12 | 13 | override suspend fun getVendors(): Result { 14 | return mutex.withLock { 15 | Result.success(VendorResult(total = 0L, vendors = vendors)) 16 | } 17 | } 18 | 19 | override suspend fun save(vendors: List) { 20 | mutex.withLock { 21 | this.vendors.clear() 22 | this.vendors.addAll(vendors) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/VendorMockRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources 2 | 3 | import cmp_delivery_application.composeapp.generated.resources.Res 4 | import com.santimattius.kmp.delivery.core.data.VendorDto 5 | import com.santimattius.kmp.delivery.core.data.asDomains 6 | import com.santimattius.kmp.delivery.core.domain.VendorResult 7 | import com.santimattius.kmp.delivery.core.network.decodeFromString 8 | import kotlinx.coroutines.delay 9 | import org.jetbrains.compose.resources.ExperimentalResourceApi 10 | 11 | class VendorMockRemoteDataSource : VendorRemoteDataSource { 12 | @OptIn(ExperimentalResourceApi::class) 13 | override suspend fun getVendors(): Result = runCatching { 14 | delay(1000L) 15 | val jsonStr = Res.readBytes("files/vendors.json").decodeToString() 16 | val response = decodeFromString>(jsonStr) 17 | VendorResult(response.size.toLong(), response.asDomains()) 18 | } 19 | } -------------------------------------------------------------------------------- /iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController(nativeViewFactory: IOSNativeView.shared) 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | class IOSNativeView : NativeViewFactory{ 14 | 15 | static let shared = IOSNativeView() 16 | 17 | func createMapView(vendors: [Vendor], onItemClick: @escaping (Vendor) -> Void) -> UIViewController { 18 | let map = NativeMapView(places: vendors, onTap: onItemClick) 19 | return UIHostingController(rootView: map) 20 | } 21 | 22 | func createVendorRow(vendor: Vendor) -> UIViewController { 23 | return UIHostingController(rootView: NativeVendorRow(vendor: vendor)) 24 | } 25 | } 26 | 27 | struct ContentView: View { 28 | 29 | var body: some View { 30 | ComposeView() 31 | } 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /iosApp/iosApp/NativeMapView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeMapView.swift 3 | // iosApp 4 | // 5 | // Created by Santiago Mattiauda on 26/5/24. 6 | // Copyright © 2024 orgName. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import MapKit 11 | import ComposeApp 12 | 13 | struct NativeMapView: View { 14 | 15 | var places = [Vendor]() 16 | var onTap:(Vendor)->Void = {_ in} 17 | 18 | @State private var region = MKCoordinateRegion( 19 | center: CLLocationCoordinate2D(latitude: -34.90111, longitude: -56.16453), 20 | span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) 21 | ) 22 | var body: some View { 23 | Map(coordinateRegion: $region,showsUserLocation: true, annotationItems: places, annotationContent: { item in 24 | MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: item.location.lat, longitude: item.location.lng)){ 25 | Image(systemName: "house.circle.fill") 26 | .font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) 27 | .foregroundColor(.red).onTapGesture { 28 | onTap(item) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | 35 | extension Vendor:Identifiable {} 36 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/com/santimattius/kmp/delivery/features/map/MapScreen.ios.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.map 2 | 3 | import LocalNativeViewFactory 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.unit.dp 10 | import androidx.compose.ui.viewinterop.UIKitInteropProperties 11 | import androidx.compose.ui.viewinterop.UIKitViewController 12 | import com.santimattius.kmp.delivery.core.domain.Vendor 13 | 14 | @Composable 15 | actual fun MapView( 16 | vendors: List, 17 | onItemClick: (Vendor) -> Unit, 18 | modifier: Modifier 19 | ) { 20 | 21 | val nativeViewFactory = LocalNativeViewFactory.current 22 | UIKitViewController( 23 | factory = { nativeViewFactory.createMapView(vendors, onItemClick) }, 24 | modifier = Modifier 25 | .fillMaxSize() 26 | .border(1.dp, Color.Black), 27 | update = {}, 28 | properties = UIKitInteropProperties( 29 | isInteractive = true, 30 | isNativeAccessibilityEnabled = true 31 | ) 32 | ) 33 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/sources/ktor/KtorVendorRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data.sources.ktor 2 | 3 | import com.santimattius.kmp.delivery.core.data.VendorDto 4 | import com.santimattius.kmp.delivery.core.data.asDomains 5 | import com.santimattius.kmp.delivery.core.data.sources.VendorRemoteDataSource 6 | import com.santimattius.kmp.delivery.core.domain.VendorResult 7 | import com.santimattius.kmp.entertainment.BuildConfig 8 | import io.ktor.client.HttpClient 9 | import io.ktor.client.call.body 10 | import io.ktor.client.request.get 11 | import io.ktor.client.request.headers 12 | 13 | class KtorVendorRemoteDataSource( 14 | private val client: HttpClient, 15 | ) : VendorRemoteDataSource { 16 | override suspend fun getVendors(): Result = runCatching { 17 | val response = client.get("/restaurants") { 18 | headers { 19 | append(AVOCODE, BuildConfig.apiKey) 20 | } 21 | } 22 | val body = response.body>() 23 | VendorResult( 24 | total = body.size.toLong(), 25 | vendors = body.asDomains() 26 | ) 27 | } 28 | 29 | companion object { 30 | private const val AVOCODE = "avocode" 31 | } 32 | } -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/com/santimattius/kmp/delivery/features/home/HomeScreen.ios.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.home 2 | 3 | import LocalNativeViewFactory 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.wrapContentSize 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.interop.UIKitViewController 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.viewinterop.UIKitInteropProperties 13 | import androidx.compose.ui.viewinterop.UIKitViewController 14 | import com.santimattius.kmp.delivery.core.domain.Vendor 15 | import kotlinx.cinterop.ExperimentalForeignApi 16 | 17 | @Composable 18 | actual fun NativeVendorItem(vendor: Vendor) { 19 | val nativeViewFactory = LocalNativeViewFactory.current 20 | UIKitViewController( 21 | factory = { nativeViewFactory.createVendorRow(vendor) }, 22 | modifier = Modifier.height(100.dp).fillMaxWidth(), 23 | update = {}, 24 | properties = UIKitInteropProperties( 25 | isInteractive = true, 26 | isNativeAccessibilityEnabled = true 27 | ) 28 | ) 29 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/network/Client.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.network 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 5 | import io.ktor.client.plugins.defaultRequest 6 | import io.ktor.client.plugins.logging.DEFAULT 7 | import io.ktor.client.plugins.logging.LogLevel 8 | import io.ktor.client.plugins.logging.Logger 9 | import io.ktor.client.plugins.logging.Logging 10 | import io.ktor.http.ContentType 11 | import io.ktor.http.contentType 12 | import io.ktor.serialization.kotlinx.json.json 13 | import kotlinx.serialization.json.Json 14 | 15 | internal fun ktorHttpClient(baseUrl: String) = HttpClient { 16 | 17 | install(ContentNegotiation) { 18 | json(json) 19 | } 20 | install(Logging) { 21 | logger = Logger.DEFAULT 22 | level = LogLevel.ALL 23 | } 24 | 25 | defaultRequest { 26 | url(baseUrl) 27 | contentType(ContentType.Application.Json) 28 | } 29 | } 30 | 31 | val json = Json { 32 | prettyPrint = true 33 | isLenient = true 34 | ignoreUnknownKeys = true 35 | allowStructuredMapKeys = true 36 | } 37 | 38 | inline fun decodeFromString(jsonStr: String): T { 39 | return json.decodeFromString(jsonStr) 40 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/data/VendorsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.data 2 | 3 | import com.santimattius.kmp.delivery.core.data.sources.VendorLocalDataSource 4 | import com.santimattius.kmp.delivery.core.data.sources.VendorRemoteDataSource 5 | import com.santimattius.kmp.delivery.core.domain.VendorResult 6 | 7 | class VendorsRepository( 8 | private val remoteDataSource: VendorRemoteDataSource, 9 | private val localDataSource: VendorLocalDataSource 10 | ) { 11 | 12 | suspend fun getVendors(): Result { 13 | val result = localDataSource.getVendors().fold( 14 | onSuccess = { 15 | if (it.vendors.isEmpty()) { 16 | Result.failure(Throwable("no vendors")) 17 | } else { 18 | Result.success(it) 19 | } 20 | }, 21 | onFailure = { Result.failure(Throwable(it.message)) }, 22 | ) 23 | if (result.isSuccess) { 24 | return result 25 | } 26 | return remoteDataSource.getVendors().fold( 27 | onSuccess = { 28 | localDataSource.save(it.vendors) 29 | Result.success(it) 30 | }, 31 | onFailure = { Result.failure(Throwable(it.message)) }, 32 | ) 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/AppBar.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.material3.TopAppBar 8 | import androidx.compose.material3.TopAppBarDefaults 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.graphics.Color 11 | 12 | 13 | @OptIn(ExperimentalMaterial3Api::class) 14 | @Composable 15 | fun AppBar( 16 | title: String = "", 17 | navigationIcon: @Composable () -> Unit = { }, 18 | containerColor: Color = MaterialTheme.colorScheme.primary, 19 | titleContentColor: Color = MaterialTheme.colorScheme.onPrimary, 20 | actions: @Composable RowScope.() -> Unit = {}, 21 | ) { 22 | TopAppBar( 23 | title = { Text(text = title) }, 24 | navigationIcon = navigationIcon, 25 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors( 26 | containerColor = containerColor, 27 | titleContentColor = titleContentColor, 28 | navigationIconContentColor = titleContentColor, 29 | actionIconContentColor = titleContentColor, 30 | ), 31 | actions = actions 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/LottieLoader.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.ui.Modifier 7 | import cmp_delivery_application.composeapp.generated.resources.Res 8 | import io.github.alexzhirkevich.compottie.LottieCompositionSpec 9 | import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState 10 | import io.github.alexzhirkevich.compottie.rememberLottieComposition 11 | import io.github.alexzhirkevich.compottie.rememberLottiePainter 12 | import org.jetbrains.compose.resources.ExperimentalResourceApi 13 | 14 | @OptIn(ExperimentalResourceApi::class) 15 | @Composable 16 | fun LottieLoader( 17 | resource: String, 18 | contentDescription: String?, 19 | modifier: Modifier = Modifier 20 | ) { 21 | val composition by rememberLottieComposition { 22 | LottieCompositionSpec.JsonString( 23 | Res.readBytes(resource).decodeToString() 24 | ) 25 | } 26 | val progress by animateLottieCompositionAsState(composition, iterations = 1000) 27 | 28 | Image( 29 | modifier = modifier, 30 | painter = rememberLottiePainter( 31 | composition = composition, 32 | progress = { progress }, 33 | ), 34 | contentDescription = contentDescription 35 | ) 36 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/santimattius/kmp/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.android 2 | 3 | import App 4 | import android.app.Activity 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.compose.setContent 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.SideEffect 11 | import androidx.compose.ui.graphics.toArgb 12 | import androidx.compose.ui.platform.LocalView 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.core.view.WindowCompat 15 | 16 | class MainActivity : ComponentActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContent { 20 | App() 21 | StatusBarColor() 22 | } 23 | } 24 | 25 | 26 | } 27 | 28 | @Composable 29 | private fun StatusBarColor() { 30 | val view = LocalView.current 31 | val colorScheme = MaterialTheme.colorScheme 32 | if (!view.isInEditMode) { 33 | SideEffect { 34 | val window = (view.context as Activity).window 35 | window.statusBarColor = colorScheme.background.copy(alpha = 0.8f).toArgb() 36 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = true 37 | } 38 | } 39 | 40 | } 41 | 42 | @Preview 43 | @Composable 44 | fun AppAndroidPreview() { 45 | App() 46 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/NetworkImage.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.material3.CircularProgressIndicator 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.layout.ContentScale 11 | import androidx.compose.ui.unit.dp 12 | import coil3.compose.LocalPlatformContext 13 | import coil3.compose.SubcomposeAsyncImage 14 | import coil3.request.ImageRequest 15 | 16 | 17 | @Composable 18 | internal fun NetworkImage( 19 | imageUrl: String, 20 | modifier: Modifier = Modifier, 21 | contentScale: ContentScale, 22 | contentDescription: String? = null, 23 | ) { 24 | SubcomposeAsyncImage( 25 | model = ImageRequest.Builder(LocalPlatformContext.current) 26 | .data(imageUrl).build(), 27 | loading = { 28 | Box(contentAlignment = Alignment.Center) { 29 | CircularProgressIndicator( 30 | color = MaterialTheme.colorScheme.secondary, 31 | modifier = Modifier.size(32.dp) 32 | ) 33 | } 34 | }, 35 | contentDescription = contentDescription, 36 | contentScale = contentScale, 37 | modifier = modifier 38 | ) 39 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/CircularAvatar.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.clip 11 | import androidx.compose.ui.graphics.RectangleShape 12 | import androidx.compose.ui.layout.ContentScale 13 | import androidx.compose.ui.unit.Dp 14 | import androidx.compose.ui.unit.dp 15 | import coil3.compose.AsyncImage 16 | import coil3.compose.LocalPlatformContext 17 | import coil3.request.ImageRequest 18 | 19 | @Composable 20 | fun CircularAvatar( 21 | image: String, 22 | contentDescription: String, 23 | modifier: Modifier = Modifier, 24 | size: Dp = 40.dp 25 | ) { 26 | Box( 27 | modifier = modifier 28 | .size(size) 29 | .background(color = MaterialTheme.colorScheme.surface, shape = RectangleShape) 30 | .clip(RectangleShape), 31 | contentAlignment = Alignment.Center 32 | ) { 33 | AsyncImage( 34 | model = ImageRequest.Builder(LocalPlatformContext.current) 35 | .data(image).build(), 36 | contentDescription = contentDescription, 37 | contentScale = ContentScale.Crop, 38 | modifier = Modifier.size(size), 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/features/splash/SplashScreen.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.splash 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.Spring 5 | import androidx.compose.animation.core.spring 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.LaunchedEffect 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.unit.dp 15 | import com.santimattius.kmp.delivery.core.ui.components.LottieLoader 16 | import kotlinx.coroutines.delay 17 | 18 | @Composable 19 | fun SplashScreen(navigate: () -> Unit) { 20 | val scale = remember { 21 | Animatable(0f) 22 | } 23 | 24 | LaunchedEffect(key1 = true) { 25 | scale.animateTo( 26 | targetValue = 0.7f, 27 | animationSpec = spring( 28 | dampingRatio = Spring.DampingRatioHighBouncy, 29 | stiffness = 1000f 30 | ) 31 | ) 32 | delay(1000L) 33 | navigate() 34 | } 35 | 36 | Box( 37 | contentAlignment = Alignment.Center, 38 | modifier = Modifier.fillMaxSize() 39 | ) { 40 | LottieLoader( 41 | modifier = Modifier.size(100.dp), 42 | resource = "files/splash.json", 43 | contentDescription = "Splash screen" 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/features/map/MapViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.map 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.santimattius.kmp.delivery.core.data.VendorsRepository 6 | import com.santimattius.kmp.delivery.core.domain.Vendor 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.SharingStarted 9 | import kotlinx.coroutines.flow.onStart 10 | import kotlinx.coroutines.flow.stateIn 11 | import kotlinx.coroutines.flow.update 12 | import kotlinx.coroutines.launch 13 | 14 | data class MapUiState( 15 | val isLoading: Boolean = false, 16 | val hasError: Boolean = false, 17 | val data: List = emptyList(), 18 | ) 19 | 20 | class MapViewModel( 21 | private val repository: VendorsRepository, 22 | ) : ViewModel() { 23 | 24 | private val _state = MutableStateFlow(MapUiState()) 25 | val state = _state 26 | .onStart { 27 | loadVendors() 28 | }.stateIn( 29 | scope = viewModelScope, 30 | started = SharingStarted.WhileSubscribed(5_000), 31 | initialValue = MapUiState(isLoading = true) 32 | ) 33 | 34 | private fun loadVendors() { 35 | viewModelScope.launch { 36 | repository.getVendors().onSuccess { result -> 37 | _state.update { 38 | it.copy(isLoading = false, data = result.vendors) 39 | } 40 | }.onFailure { 41 | _state.update { it.copy(isLoading = false, hasError = true) } 42 | } 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/themes/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.themes 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | import com.santimattius.kmp.entertainment.core.ui.themes.Pink40 10 | import com.santimattius.kmp.entertainment.core.ui.themes.Pink80 11 | import com.santimattius.kmp.entertainment.core.ui.themes.Purple40 12 | import com.santimattius.kmp.entertainment.core.ui.themes.Purple80 13 | import com.santimattius.kmp.entertainment.core.ui.themes.PurpleGrey40 14 | import com.santimattius.kmp.entertainment.core.ui.themes.PurpleGrey80 15 | import com.santimattius.kmp.entertainment.core.ui.themes.Typography 16 | 17 | private val DarkColorScheme = darkColorScheme( 18 | primary = Purple80, 19 | secondary = PurpleGrey80, 20 | tertiary = Pink80 21 | ) 22 | 23 | private val LightColorScheme = lightColorScheme( 24 | primary = Purple40, 25 | secondary = PurpleGrey40, 26 | tertiary = Pink40, 27 | onPrimary = Color.White, 28 | background = Color.White, 29 | surfaceContainer = Color.White, 30 | surface = Color.White, 31 | ) 32 | 33 | @Composable 34 | fun AppTheme( 35 | darkTheme: Boolean = isSystemInDarkTheme(), 36 | content: @Composable () -> Unit, 37 | ) { 38 | val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme 39 | MaterialTheme( 40 | colorScheme = colorScheme, 41 | typography = Typography, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | CADisableMinimumFrameDurationOnPhone 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.navigation.NavHostController 6 | import androidx.navigation.compose.NavHost 7 | import androidx.navigation.compose.composable 8 | import androidx.navigation.compose.rememberNavController 9 | import com.santimattius.kmp.delivery.di.applicationModules 10 | import com.santimattius.kmp.delivery.features.home.HomeScreen 11 | import com.santimattius.kmp.delivery.features.map.MapScreen 12 | import com.santimattius.kmp.delivery.features.splash.SplashScreen 13 | import kotlinx.serialization.Serializable 14 | import org.koin.compose.KoinApplication 15 | 16 | @Composable 17 | fun MainApplication() { 18 | KoinApplication(application = { 19 | modules(applicationModules()) 20 | }) { 21 | AppNavigation() 22 | } 23 | } 24 | 25 | @Composable 26 | fun AppNavigation( 27 | modifier: Modifier = Modifier, 28 | navController: NavHostController = rememberNavController(), 29 | ) { 30 | 31 | NavHost( 32 | modifier = modifier, 33 | navController = navController, 34 | startDestination = Splash 35 | ) { 36 | composable { 37 | SplashScreen { 38 | navController.popBackStack() 39 | navController.navigate(Home) 40 | } 41 | } 42 | composable { 43 | HomeScreen { 44 | navController.navigate(Map) 45 | } 46 | } 47 | composable { 48 | MapScreen { 49 | navController.popBackStack() 50 | } 51 | } 52 | } 53 | 54 | 55 | } 56 | 57 | @Serializable 58 | data object Splash 59 | 60 | @Serializable 61 | data object Home 62 | 63 | @Serializable 64 | data object Map -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/di/Dependencies.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.di 2 | 3 | import com.santimattius.kmp.delivery.core.data.VendorsRepository 4 | import com.santimattius.kmp.delivery.core.data.sources.VendorLocalDataSource 5 | import com.santimattius.kmp.delivery.core.data.sources.VendorMockRemoteDataSource 6 | import com.santimattius.kmp.delivery.core.data.sources.VendorRemoteDataSource 7 | import com.santimattius.kmp.delivery.core.data.sources.ktor.KtorVendorRemoteDataSource 8 | import com.santimattius.kmp.delivery.core.data.sources.local.InMemoryVendorLocalDataSource 9 | import com.santimattius.kmp.delivery.core.network.ktorHttpClient 10 | import com.santimattius.kmp.delivery.features.home.HomeScreenViewModel 11 | import com.santimattius.kmp.delivery.features.map.MapViewModel 12 | import org.koin.compose.viewmodel.dsl.viewModelOf 13 | import org.koin.core.qualifier.named 14 | import org.koin.core.qualifier.qualifier 15 | import org.koin.dsl.module 16 | 17 | val sharedModules = module { 18 | single(qualifier(AppQualifiers.BaseUrl)) { "https://ts-mock-api.onrender.com" } 19 | single(qualifier(AppQualifiers.Client)) { 20 | ktorHttpClient( 21 | baseUrl = get( 22 | qualifier = qualifier( 23 | AppQualifiers.BaseUrl 24 | ) 25 | ) 26 | ) 27 | } 28 | 29 | single(named(AppQualifiers.RemoteSource)) { 30 | KtorVendorRemoteDataSource( 31 | get(qualifier(AppQualifiers.Client)) 32 | ) 33 | } 34 | 35 | single(named(AppQualifiers.FileSource)) { 36 | VendorMockRemoteDataSource() 37 | } 38 | single { InMemoryVendorLocalDataSource() } 39 | single { VendorsRepository(get(named(AppQualifiers.FileSource)), get()) } 40 | } 41 | 42 | val homeModule = module { 43 | viewModelOf(::HomeScreenViewModel) 44 | viewModelOf(::MapViewModel) 45 | } 46 | 47 | 48 | fun applicationModules() = listOf(sharedModules, homeModule) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/features/home/HomeScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.home 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.santimattius.kmp.delivery.core.data.VendorsRepository 6 | import com.santimattius.kmp.delivery.core.domain.Vendor 7 | import kotlinx.coroutines.CoroutineExceptionHandler 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.SharingStarted 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.flow.onStart 12 | import kotlinx.coroutines.flow.stateIn 13 | import kotlinx.coroutines.flow.update 14 | import kotlinx.coroutines.launch 15 | 16 | 17 | data class HomeUiState( 18 | val isLoading: Boolean = false, 19 | val hasError: Boolean = false, 20 | val data: List = emptyList(), 21 | val total: Long = 0, 22 | ) 23 | 24 | class HomeScreenViewModel( 25 | private val repository: VendorsRepository, 26 | ) : ViewModel() { 27 | 28 | private val _state = MutableStateFlow(HomeUiState()) 29 | val state: StateFlow = _state 30 | .onStart { 31 | loadVendors() 32 | }.stateIn( 33 | scope = viewModelScope, 34 | started = SharingStarted.WhileSubscribed(5000), 35 | initialValue = HomeUiState() 36 | ) 37 | 38 | private val exceptionHandler = CoroutineExceptionHandler { _, _ -> 39 | _state.update { it.copy(isLoading = false, hasError = true) } 40 | } 41 | 42 | private fun loadVendors() { 43 | _state.update { it.copy(isLoading = true, hasError = false) } 44 | viewModelScope.launch(exceptionHandler) { 45 | repository.getVendors().onSuccess { result -> 46 | _state.update { 47 | it.copy( 48 | isLoading = false, 49 | data = result.vendors, 50 | total = result.total 51 | ) 52 | } 53 | }.onFailure { 54 | _state.update { it.copy(isLoading = false, hasError = true) } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /iosApp/iosApp/NativeVendorRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativeVendorRow.swift 3 | // iosApp 4 | // 5 | // Created by Santiago Mattiauda on 26/8/24. 6 | // Copyright © 2024 orgName. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import ComposeApp 11 | 12 | struct NativeVendorRow: View { 13 | 14 | let vendor: Vendor 15 | 16 | var body: some View { 17 | HStack { 18 | AsyncImage(url: URL(string: vendor.logo)) { image in 19 | image.resizable().cornerRadius(8.0) 20 | } placeholder: { 21 | ProgressView() 22 | } 23 | .frame(width: 50, height: 50) 24 | 25 | VStack(alignment: .leading) { 26 | Text(vendor.name) 27 | .font(.headline) 28 | .lineLimit(1) 29 | Text(vendor.categories.joined(separator: "*")) 30 | .font(.subheadline) 31 | .foregroundColor(.gray) 32 | HStack { 33 | Text(vendor.deliveryTime) 34 | Text("\(vendor.deliveryFee.formatted())") 35 | } 36 | .font(.subheadline) 37 | .foregroundColor(.gray) 38 | } 39 | 40 | Spacer() 41 | 42 | HStack(spacing: 2) { 43 | Image(systemName: "star.fill") 44 | .foregroundColor(.orange) 45 | Text("\(vendor.rating.formatted())") 46 | .font(.subheadline) 47 | .foregroundColor(.gray) 48 | } 49 | } 50 | .frame(height: 100) 51 | .padding(.vertical, 5) 52 | .padding(.horizontal, 10) 53 | } 54 | } 55 | 56 | 57 | #Preview { 58 | NativeVendorRow( 59 | vendor:Vendor( 60 | id: 12, name: "Hello", 61 | logo:"https://images.deliveryhero.io/image/pedidosya/restaurants/logo-haprh3.jpg", 62 | location: Location_(lat: 0.0, lng: 0.0), 63 | isNew: false, 64 | isExclusive: false, 65 | deliveryTime: "30-45 min.", 66 | deliveryFee: 10, 67 | rating: 1, 68 | headerImage: "", 69 | categories: ["Pizza"] 70 | ) 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMP - Delivery Application 2 | 3 | This is a Kotlin Multiplatform project targeting Android, iOS. 4 | 5 | * `/composeApp` is for code that will be shared across your Compose Multiplatform applications. 6 | It contains several subfolders: 7 | - `commonMain` is for code that’s common for all targets. 8 | - Other folders are for Kotlin code that will be compiled for only the platform indicated in the 9 | folder name. 10 | For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, 11 | `iosMain` would be the right folder for such calls. 12 | 13 | * `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, 14 | you need this entry point for your iOS app. This is also where you should add SwiftUI code for 15 | your project. 16 | 17 | 18 | 19 | https://github.com/santimattius/cmp-delivery-application/assets/22333101/72d1f390-943e-4b31-9572-4e79822d19c1 20 | 21 | 22 | ## Prepare the environment 23 | 24 | - Install and configure the latest JDK 17+. 25 | - If you have Gradle installed, make sure you use Gradle 8.1 or later. 26 | - Install and configure the latest Android Studio for Android samples. 27 | - Install and configure the latest Xcode for iOS samples. 28 | 29 | Use the [KDoctor](https://github.com/Kotlin/kdoctor) tool to ensure that your development 30 | environment is configured correctly: 31 | 32 | 1. Install KDoctor with [Homebrew](https://brew.sh/): 33 | 34 | ```text 35 | brew install kdoctor 36 | ``` 37 | 38 | 2. Run KDoctor in your terminal: 39 | 40 | ```text 41 | kdoctor 42 | ``` 43 | 44 | If everything is set up correctly, you'll see valid output: 45 | 46 | ```text 47 | Environment diagnose (to see all details, use -v option): 48 | [✓] Operation System 49 | [✓] Java 50 | [✓] Android Studio 51 | [✓] Xcode 52 | [✓] Cocoapods 53 | 54 | Conclusion: 55 | ✓ Your system is ready for Kotlin Multiplatform Mobile development! 56 | ``` 57 | 58 | Otherwise, KDoctor will highlight which parts of your setup still need to be configured and will 59 | suggest a way to fix 60 | them. 61 | 62 | ## Setup 63 | 64 | Using local properties for define api key: 65 | 66 | ```properties 67 | apiKey="{your-api-key}" 68 | ``` 69 | 70 | Learn more 71 | about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html) 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/core/ui/components/SearchAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.core.ui.components 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.filled.Menu 12 | import androidx.compose.material.icons.filled.Search 13 | import androidx.compose.material.icons.filled.ShoppingCart 14 | import androidx.compose.material3.ExperimentalMaterial3Api 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.material3.TextField 19 | import androidx.compose.material3.TextFieldDefaults 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.graphics.Color 24 | import androidx.compose.ui.unit.dp 25 | 26 | @Composable 27 | fun SearchAppBar( 28 | text: String = "", 29 | onValueChange: (String) -> Unit = {}, 30 | modifier: Modifier = Modifier, 31 | ) { 32 | Row( 33 | modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), 34 | horizontalArrangement = Arrangement.Center, 35 | verticalAlignment = Alignment.CenterVertically 36 | ) { 37 | Icon( 38 | modifier = Modifier.weight(0.5f), 39 | imageVector = Icons.Default.Menu, 40 | contentDescription = "" 41 | ) 42 | TextField( 43 | value = text, 44 | onValueChange = { 45 | onValueChange(it) 46 | }, 47 | modifier = modifier.border( 48 | BorderStroke(width = 1.dp, color = Color.LightGray), 49 | shape = RoundedCornerShape(20) 50 | ), 51 | colors = TextFieldDefaults.colors( 52 | focusedContainerColor = Color.Transparent, 53 | unfocusedContainerColor = Color.Transparent, 54 | disabledContainerColor = Color.Transparent, 55 | unfocusedIndicatorColor = Color.Transparent, 56 | focusedIndicatorColor = Color.Transparent, 57 | disabledIndicatorColor = Color.Transparent, 58 | errorIndicatorColor = Color.Transparent, 59 | errorLabelColor = Color.Transparent, 60 | errorCursorColor = Color.Transparent, 61 | errorContainerColor = Color.Transparent, 62 | ), 63 | textStyle = MaterialTheme.typography.bodyMedium, 64 | trailingIcon = { 65 | Icon( 66 | imageVector = Icons.Default.Search, 67 | contentDescription = "" 68 | ) 69 | }, 70 | placeholder = { 71 | Text(text = "Buscar locales y platos") 72 | } 73 | ) 74 | Icon( 75 | modifier = Modifier.weight(0.5f), 76 | imageVector = Icons.Default.ShoppingCart, 77 | contentDescription = "" 78 | ) 79 | } 80 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/features/map/MapScreen.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.map 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.automirrored.filled.ArrowBack 8 | import androidx.compose.material3.BottomSheetDefaults 9 | import androidx.compose.material3.ExperimentalMaterial3Api 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.material3.IconButton 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.ModalBottomSheet 14 | import androidx.compose.material3.Scaffold 15 | import androidx.compose.material3.rememberModalBottomSheetState 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.getValue 18 | import androidx.compose.runtime.mutableStateOf 19 | import androidx.compose.runtime.remember 20 | import androidx.compose.runtime.setValue 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 24 | import cmp_delivery_application.composeapp.generated.resources.Res 25 | import cmp_delivery_application.composeapp.generated.resources.app_name 26 | import com.santimattius.kmp.delivery.core.domain.Vendor 27 | import com.santimattius.kmp.delivery.core.ui.components.AppBar 28 | import com.santimattius.kmp.delivery.core.ui.components.LoadingIndicator 29 | import com.santimattius.kmp.delivery.features.home.VendorRowCard 30 | import org.jetbrains.compose.resources.stringResource 31 | import org.koin.compose.viewmodel.koinViewModel 32 | import org.koin.core.annotation.KoinExperimentalAPI 33 | 34 | @OptIn(KoinExperimentalAPI::class) 35 | @Composable 36 | fun MapScreen( 37 | viewModel: MapViewModel = koinViewModel(), 38 | modifier: Modifier = Modifier, 39 | onBack: () -> Unit = {}, 40 | ) { 41 | 42 | var select by remember { mutableStateOf(null) } 43 | 44 | Scaffold( 45 | modifier = modifier, 46 | topBar = { 47 | AppBar( 48 | title = stringResource(Res.string.app_name), 49 | containerColor = MaterialTheme.colorScheme.background, 50 | titleContentColor = Color.Black, 51 | navigationIcon = { 52 | IconButton(onClick = onBack) { 53 | Icon( 54 | imageVector = Icons.AutoMirrored.Default.ArrowBack, 55 | contentDescription = null 56 | ) 57 | } 58 | }) 59 | } 60 | ) { 61 | val state by viewModel.state.collectAsStateWithLifecycle() 62 | Box( 63 | modifier = Modifier.fillMaxSize().padding(it) 64 | ) { 65 | when { 66 | state.isLoading -> LoadingIndicator() 67 | else -> { 68 | MapView(vendors = state.data, onItemClick = { current -> select = current }) 69 | } 70 | } 71 | } 72 | } 73 | 74 | if (select != null) { 75 | BottomSheet(select!!) { 76 | select = null 77 | } 78 | } 79 | } 80 | 81 | @Composable 82 | expect fun MapView( 83 | vendors: List, 84 | onItemClick: (Vendor) -> Unit, 85 | modifier: Modifier = Modifier, 86 | ) 87 | 88 | @OptIn(ExperimentalMaterial3Api::class) 89 | @Composable 90 | fun BottomSheet(vendor: Vendor, onDismiss: () -> Unit) { 91 | val modalBottomSheetState = rememberModalBottomSheetState() 92 | 93 | ModalBottomSheet( 94 | onDismissRequest = { onDismiss() }, 95 | sheetState = modalBottomSheetState, 96 | dragHandle = { BottomSheetDefaults.DragHandle() }, 97 | ) { 98 | VendorRowCard(vendor) 99 | } 100 | } -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.7.2" 3 | kotlin = "2.0.21" 4 | compose = "1.7.5" 5 | compose-plugin = "1.7.0" 6 | skieVersion = "0.9.3" 7 | 8 | android-compileSdk = "35" 9 | android-minSdk = "24" 10 | android-targetSdk = "35" 11 | 12 | androidx-core-ktx = "1.15.0" 13 | androidx-activityCompose = "1.9.3" 14 | androidx-appcompat = "1.7.0" 15 | 16 | lifecycleRuntime = "2.8.7" 17 | lifecycleCompose = "2.8.3" 18 | navigationCompose = "2.8.0-alpha08" 19 | 20 | statelyCommon = "2.1.0" 21 | coil = "3.0.3" 22 | 23 | koin = "4.0.0" 24 | 25 | kotlinxCoroutinesAndroid = "1.9.0" 26 | ktorVersion = "3.0.1" 27 | 28 | compottie = "2.0.0-beta02" 29 | 30 | buildkonfigGradlePlugin = "0.15.1" 31 | 32 | [libraries] 33 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } 34 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } 35 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } 36 | compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } 37 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } 38 | 39 | stately-common = { module = "co.touchlab:stately-common", version.ref = "statelyCommon" } 40 | 41 | lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleCompose" } 42 | lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleCompose" } 43 | androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycleRuntime" } 44 | navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } 45 | 46 | coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } 47 | coil-network = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } 48 | 49 | koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } 50 | koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } 51 | koin-compose-viewModel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } 52 | koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } 53 | 54 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesAndroid" } 55 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } 56 | ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorVersion" } 57 | 58 | ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktorVersion" } 59 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorVersion" } 60 | ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktorVersion" } 61 | ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorVersion" } 62 | ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorVersion" } 63 | 64 | compottie-core = { module = "io.github.alexzhirkevich:compottie", version.ref = "compottie" } 65 | compottie-dot = { module = "io.github.alexzhirkevich:compottie-dot", version.ref = "compottie" } 66 | compottie-network = { module = "io.github.alexzhirkevich:compottie-network", version.ref = "compottie" } 67 | compottie-resources = { module = "io.github.alexzhirkevich:compottie-resources", version.ref = "compottie" } 68 | 69 | buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" } 70 | 71 | [plugins] 72 | androidApplication = { id = "com.android.application", version.ref = "agp" } 73 | androidLibrary = { id = "com.android.library", version.ref = "agp" } 74 | jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } 75 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 76 | kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 77 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 78 | skie = { id = "co.touchlab.skie", version.ref = "skieVersion" } -------------------------------------------------------------------------------- /composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.codingfeline.buildkonfig.compiler.FieldSpec 2 | import org.jetbrains.compose.internal.utils.getLocalProperty 3 | 4 | plugins { 5 | alias(libs.plugins.kotlinMultiplatform) 6 | alias(libs.plugins.androidApplication) 7 | alias(libs.plugins.jetbrainsCompose) 8 | alias(libs.plugins.compose.compiler) 9 | alias(libs.plugins.kotlinSerialization) 10 | alias(libs.plugins.skie) 11 | id("com.codingfeline.buildkonfig") 12 | } 13 | 14 | kotlin { 15 | applyDefaultHierarchyTemplate() 16 | androidTarget { 17 | compilations.all { 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | } 22 | } 23 | 24 | listOf( 25 | iosX64(), 26 | iosArm64(), 27 | iosSimulatorArm64() 28 | ).forEach { iosTarget -> 29 | iosTarget.binaries.framework { 30 | baseName = "ComposeApp" 31 | isStatic = true 32 | } 33 | } 34 | 35 | sourceSets { 36 | 37 | androidMain.dependencies { 38 | implementation(libs.compose.ui.tooling.preview) 39 | implementation(libs.androidx.activity.compose) 40 | 41 | api(libs.androidx.activity.compose) 42 | api(libs.androidx.appcompat) 43 | api(libs.androidx.core.ktx) 44 | 45 | implementation(libs.ktor.client.okhttp) 46 | implementation(libs.kotlinx.coroutines.android) 47 | 48 | implementation(libs.koin.android) 49 | } 50 | commonMain.dependencies { 51 | implementation(compose.runtime) 52 | implementation(compose.foundation) 53 | implementation(compose.material) 54 | implementation(compose.material3) 55 | implementation(compose.materialIconsExtended) 56 | implementation(compose.ui) 57 | implementation(compose.components.resources) 58 | 59 | implementation(libs.coil.compose) 60 | implementation(libs.coil.network) 61 | 62 | implementation(libs.stately.common) 63 | 64 | implementation(libs.lifecycle.viewmodel.compose) 65 | implementation(libs.androidx.lifecycle.runtime) 66 | implementation(libs.lifecycle.runtime.compose) 67 | implementation(libs.navigation.compose) 68 | 69 | implementation(libs.ktor.client.core) 70 | implementation(libs.ktor.client.content.negotiation) 71 | implementation(libs.ktor.client.logging) 72 | implementation(libs.ktor.serialization.kotlinx.json) 73 | implementation(libs.kotlinx.coroutines.core) 74 | 75 | api(libs.koin.core) 76 | api(libs.koin.compose) 77 | api(libs.koin.compose.viewModel) 78 | 79 | implementation(libs.compottie.core) 80 | implementation(libs.compottie.dot) 81 | implementation(libs.compottie.network) 82 | implementation(libs.compottie.resources) 83 | 84 | } 85 | 86 | iosMain.dependencies { 87 | implementation(libs.ktor.client.darwin) 88 | } 89 | } 90 | } 91 | 92 | composeCompiler { 93 | enableStrongSkippingMode = true 94 | } 95 | 96 | buildkonfig { 97 | packageName = "com.santimattius.kmp.entertainment" 98 | objectName = "BuildConfig" 99 | 100 | defaultConfigs { 101 | buildConfigField(FieldSpec.Type.STRING, "apiKey", getLocalProperty("apiKey")) 102 | } 103 | } 104 | android { 105 | namespace = "com.santimattius.kmp.compose.skeleton" 106 | compileSdk = libs.versions.android.compileSdk.get().toInt() 107 | 108 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 109 | sourceSets["main"].res.srcDirs("src/androidMain/res") 110 | sourceSets["main"].resources.srcDirs("src/commonMain/resources") 111 | 112 | defaultConfig { 113 | applicationId = "com.santimattius.kmp.compose.skeleton" 114 | minSdk = libs.versions.android.minSdk.get().toInt() 115 | targetSdk = libs.versions.android.targetSdk.get().toInt() 116 | versionCode = 1 117 | versionName = "1.0" 118 | } 119 | packaging { 120 | resources { 121 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 122 | } 123 | } 124 | buildTypes { 125 | getByName("release") { 126 | isMinifyEnabled = false 127 | } 128 | } 129 | compileOptions { 130 | sourceCompatibility = JavaVersion.VERSION_11 131 | targetCompatibility = JavaVersion.VERSION_11 132 | } 133 | dependencies { 134 | debugImplementation(libs.compose.ui.tooling) 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 58 | 62 | 63 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/santimattius/kmp/delivery/features/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.santimattius.kmp.delivery.features.home 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.PaddingValues 9 | import androidx.compose.foundation.layout.Row 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.foundation.layout.aspectRatio 12 | import androidx.compose.foundation.layout.fillMaxHeight 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.fillMaxWidth 15 | import androidx.compose.foundation.layout.padding 16 | import androidx.compose.foundation.layout.size 17 | import androidx.compose.foundation.lazy.LazyColumn 18 | import androidx.compose.foundation.lazy.itemsIndexed 19 | import androidx.compose.material.icons.Icons 20 | import androidx.compose.material.icons.automirrored.filled.ViewList 21 | import androidx.compose.material.icons.filled.GridView 22 | import androidx.compose.material.icons.filled.Map 23 | import androidx.compose.material.icons.filled.Star 24 | import androidx.compose.material3.Card 25 | import androidx.compose.material3.CardDefaults 26 | import androidx.compose.material3.FloatingActionButton 27 | import androidx.compose.material3.HorizontalDivider 28 | import androidx.compose.material3.Icon 29 | import androidx.compose.material3.IconButton 30 | import androidx.compose.material3.ListItem 31 | import androidx.compose.material3.ListItemDefaults 32 | import androidx.compose.material3.MaterialTheme 33 | import androidx.compose.material3.Scaffold 34 | import androidx.compose.material3.Text 35 | import androidx.compose.runtime.Composable 36 | import androidx.compose.runtime.getValue 37 | import androidx.compose.runtime.mutableStateOf 38 | import androidx.compose.runtime.remember 39 | import androidx.compose.runtime.setValue 40 | import androidx.compose.ui.Alignment 41 | import androidx.compose.ui.Modifier 42 | import androidx.compose.ui.graphics.Color 43 | import androidx.compose.ui.graphics.Color.Companion.LightGray 44 | import androidx.compose.ui.layout.ContentScale 45 | import androidx.compose.ui.text.style.TextOverflow 46 | import androidx.compose.ui.unit.Dp 47 | import androidx.compose.ui.unit.dp 48 | import androidx.compose.ui.unit.sp 49 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 50 | import com.santimattius.kmp.delivery.core.domain.Vendor 51 | import com.santimattius.kmp.delivery.core.ui.components.CircularAvatar 52 | import com.santimattius.kmp.delivery.core.ui.components.ErrorView 53 | import com.santimattius.kmp.delivery.core.ui.components.LoadingIndicator 54 | import com.santimattius.kmp.delivery.core.ui.components.NetworkImage 55 | import com.santimattius.kmp.delivery.core.ui.components.SearchAppBar 56 | import org.koin.compose.viewmodel.koinViewModel 57 | import org.koin.core.annotation.KoinExperimentalAPI 58 | 59 | @Composable 60 | fun HomeScreen( 61 | screenModel: HomeScreenViewModel = koinViewModel(), 62 | onOpenMap: () -> Unit = {}, 63 | ) { 64 | 65 | Scaffold( 66 | topBar = { SearchAppBar() }, 67 | floatingActionButton = { 68 | FloatingActionButton(onClick = onOpenMap) { 69 | Icon(Icons.Default.Map, contentDescription = null) 70 | } 71 | } 72 | ) { 73 | val state by screenModel.state.collectAsStateWithLifecycle() 74 | Box( 75 | modifier = Modifier.fillMaxSize().padding(it), 76 | contentAlignment = Alignment.Center 77 | ) { 78 | when { 79 | state.isLoading -> LoadingIndicator() 80 | 81 | state.hasError -> { 82 | ErrorView(message = "Something went wrong") 83 | } 84 | 85 | else -> { 86 | VendorList(total = state.total, vendors = state.data) 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | @Composable 94 | private fun VendorList( 95 | total: Long, 96 | vendors: List, 97 | onItemClick: (Vendor) -> Unit = {}, 98 | modifier: Modifier = Modifier, 99 | ) { 100 | var isCard by remember { mutableStateOf(false) } 101 | 102 | LazyColumn( 103 | contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp), 104 | modifier = modifier 105 | ) { 106 | item { 107 | VendorHeader( 108 | total = total, 109 | isCard = isCard, 110 | modifier = Modifier 111 | .background(MaterialTheme.colorScheme.background) 112 | .fillMaxWidth(), 113 | onViewTypeClicked = { isCard = !isCard }, 114 | ) 115 | } 116 | itemsIndexed(vendors, key = { _, item -> item.id }) { index, vendor -> 117 | if (isCard) { 118 | VendorRowCard( 119 | modifier = Modifier.padding( 120 | vertical = 4.dp, 121 | ), 122 | elevation = 2.dp, 123 | vendor = vendor 124 | ) 125 | } else { 126 | NativeVendorItem(vendor = vendor) 127 | if (index < vendors.lastIndex) { 128 | HorizontalDivider( 129 | color = LightGray, 130 | thickness = 1.dp, 131 | modifier = Modifier.padding( 132 | top = 8.dp, bottom = 8.dp, start = 16.dp 133 | ) 134 | ) 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | @Composable 142 | private fun VendorHeader( 143 | modifier: Modifier = Modifier, 144 | total: Long, 145 | isCard: Boolean, 146 | onViewTypeClicked: () -> Unit = {}, 147 | ) { 148 | Row( 149 | modifier = modifier, 150 | horizontalArrangement = Arrangement.Center, 151 | verticalAlignment = Alignment.CenterVertically 152 | ) { 153 | Text( 154 | text = "$total restaurantes", 155 | style = MaterialTheme.typography.titleLarge, 156 | modifier = Modifier.weight(1f) 157 | ) 158 | IconButton( 159 | onClick = onViewTypeClicked, 160 | modifier = Modifier 161 | .weight(0.1f) 162 | ) { 163 | Icon( 164 | imageVector = if (!isCard) Icons.Filled.GridView else Icons.AutoMirrored.Filled.ViewList, 165 | contentDescription = if (!isCard) "Change to Card" else "Change to Simple", 166 | ) 167 | } 168 | } 169 | } 170 | 171 | @Composable 172 | fun VendorRowCard( 173 | vendor: Vendor, 174 | modifier: Modifier = Modifier, 175 | elevation: Dp = 0.dp, 176 | ) { 177 | Card( 178 | modifier = modifier, 179 | elevation = CardDefaults.elevatedCardElevation(defaultElevation = elevation), 180 | colors = CardDefaults.cardColors( 181 | containerColor = MaterialTheme.colorScheme.surface, 182 | contentColor = MaterialTheme.colorScheme.onSurface 183 | ) 184 | ) { 185 | Column { 186 | NetworkImage( 187 | imageUrl = vendor.headerImage, 188 | contentDescription = "Image", 189 | contentScale = ContentScale.Crop, 190 | modifier = Modifier.fillMaxWidth() 191 | .background(LightGray.copy(alpha = 0.3f)) 192 | .aspectRatio(ratio = (16 / 8).toFloat()), 193 | ) 194 | VendorRowItem(vendor) 195 | } 196 | } 197 | } 198 | 199 | 200 | @Composable 201 | internal fun VendorRowItem( 202 | vendor: Vendor, 203 | onItemClick: (Vendor) -> Unit = {}, 204 | ) { 205 | ListItem( 206 | colors = ListItemDefaults.colors( 207 | containerColor = MaterialTheme.colorScheme.surface, 208 | ), 209 | modifier = Modifier.clickable { onItemClick(vendor) }, 210 | leadingContent = { 211 | CircularAvatar( 212 | image = vendor.logo, contentDescription = vendor.name, size = 60.dp 213 | ) 214 | }, 215 | headlineContent = { 216 | Text( 217 | text = vendor.name, 218 | style = MaterialTheme.typography.titleMedium, 219 | maxLines = 1, 220 | fontSize = 16.sp, 221 | overflow = TextOverflow.Ellipsis 222 | ) 223 | }, 224 | supportingContent = { 225 | Column( 226 | horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top 227 | ) { 228 | if (vendor.categories.isNotEmpty()) { 229 | Text(text = vendor.categories.joinToString("·"), fontSize = 12.sp) 230 | } 231 | Text(text = "${vendor.deliveryTime}·Envio ${vendor.deliveryFee}", fontSize = 12.sp) 232 | Spacer(modifier = Modifier.fillMaxHeight()) 233 | } 234 | }, 235 | trailingContent = { 236 | Row( 237 | horizontalArrangement = Arrangement.Center, 238 | verticalAlignment = Alignment.CenterVertically 239 | ) { 240 | Icon( 241 | imageVector = Icons.Default.Star, 242 | contentDescription = "", 243 | tint = Color(0xfff86601), 244 | modifier = Modifier.size(16.dp) 245 | ) 246 | Text(text = vendor.rating.toString(), fontSize = 12.sp) 247 | } 248 | } 249 | ) 250 | } 251 | 252 | @Composable 253 | expect fun NativeVendorItem(vendor: Vendor) -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 11 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 12 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 13 | 4B19789D2C03974A00472307 /* NativeMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B19789C2C03974A00472307 /* NativeMapView.swift */; }; 14 | 4B5CBE9A2C7D3F3E003F715D /* NativeVendorRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5CBE992C7D3F3D003F715D /* NativeVendorRow.swift */; }; 15 | 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 20 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 21 | 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 22 | 4B19789C2C03974A00472307 /* NativeMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeMapView.swift; sourceTree = ""; }; 23 | 4B5CBE992C7D3F3D003F715D /* NativeVendorRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVendorRow.swift; sourceTree = ""; }; 24 | 7555FF7B242A565900829871 /* kmp-compose-gradle-skeleton.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "kmp-compose-gradle-skeleton.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 26 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXGroup section */ 31 | 058557D7273AAEEB004C7B11 /* Preview Content */ = { 32 | isa = PBXGroup; 33 | children = ( 34 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, 35 | ); 36 | path = "Preview Content"; 37 | sourceTree = ""; 38 | }; 39 | 42799AB246E5F90AF97AA0EF /* Frameworks */ = { 40 | isa = PBXGroup; 41 | children = ( 42 | ); 43 | name = Frameworks; 44 | sourceTree = ""; 45 | }; 46 | 7555FF72242A565900829871 = { 47 | isa = PBXGroup; 48 | children = ( 49 | AB1DB47929225F7C00F7AF9C /* Configuration */, 50 | 7555FF7D242A565900829871 /* iosApp */, 51 | 7555FF7C242A565900829871 /* Products */, 52 | 42799AB246E5F90AF97AA0EF /* Frameworks */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 7555FF7C242A565900829871 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 7555FF7B242A565900829871 /* kmp-compose-gradle-skeleton.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 7555FF7D242A565900829871 /* iosApp */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 058557BA273AAA24004C7B11 /* Assets.xcassets */, 68 | 7555FF82242A565900829871 /* ContentView.swift */, 69 | 7555FF8C242A565B00829871 /* Info.plist */, 70 | 2152FB032600AC8F00CF470E /* iOSApp.swift */, 71 | 058557D7273AAEEB004C7B11 /* Preview Content */, 72 | 4B19789C2C03974A00472307 /* NativeMapView.swift */, 73 | 4B5CBE992C7D3F3D003F715D /* NativeVendorRow.swift */, 74 | ); 75 | path = iosApp; 76 | sourceTree = ""; 77 | }; 78 | AB1DB47929225F7C00F7AF9C /* Configuration */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | AB3632DC29227652001CCB65 /* Config.xcconfig */, 82 | ); 83 | path = Configuration; 84 | sourceTree = ""; 85 | }; 86 | /* End PBXGroup section */ 87 | 88 | /* Begin PBXNativeTarget section */ 89 | 7555FF7A242A565900829871 /* iosApp */ = { 90 | isa = PBXNativeTarget; 91 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; 92 | buildPhases = ( 93 | F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, 94 | 7555FF77242A565900829871 /* Sources */, 95 | 7555FF79242A565900829871 /* Resources */, 96 | ); 97 | buildRules = ( 98 | ); 99 | dependencies = ( 100 | ); 101 | name = iosApp; 102 | productName = iosApp; 103 | productReference = 7555FF7B242A565900829871 /* kmp-compose-gradle-skeleton.app */; 104 | productType = "com.apple.product-type.application"; 105 | }; 106 | /* End PBXNativeTarget section */ 107 | 108 | /* Begin PBXProject section */ 109 | 7555FF73242A565900829871 /* Project object */ = { 110 | isa = PBXProject; 111 | attributes = { 112 | LastSwiftUpdateCheck = 1130; 113 | LastUpgradeCheck = 1130; 114 | ORGANIZATIONNAME = orgName; 115 | TargetAttributes = { 116 | 7555FF7A242A565900829871 = { 117 | CreatedOnToolsVersion = 11.3.1; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; 122 | compatibilityVersion = "Xcode 9.3"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 7555FF72242A565900829871; 130 | productRefGroup = 7555FF7C242A565900829871 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 7555FF7A242A565900829871 /* iosApp */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 7555FF79242A565900829871 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, 145 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXResourcesBuildPhase section */ 150 | 151 | /* Begin PBXShellScriptBuildPhase section */ 152 | F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { 153 | isa = PBXShellScriptBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | ); 157 | inputFileListPaths = ( 158 | ); 159 | inputPaths = ( 160 | ); 161 | name = "Compile Kotlin Framework"; 162 | outputFileListPaths = ( 163 | ); 164 | outputPaths = ( 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | shellPath = /bin/sh; 168 | shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; 169 | }; 170 | /* End PBXShellScriptBuildPhase section */ 171 | 172 | /* Begin PBXSourcesBuildPhase section */ 173 | 7555FF77242A565900829871 /* Sources */ = { 174 | isa = PBXSourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 4B19789D2C03974A00472307 /* NativeMapView.swift in Sources */, 178 | 4B5CBE9A2C7D3F3E003F715D /* NativeVendorRow.swift in Sources */, 179 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, 180 | 7555FF83242A565900829871 /* ContentView.swift in Sources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXSourcesBuildPhase section */ 185 | 186 | /* Begin XCBuildConfiguration section */ 187 | 7555FFA3242A565B00829871 /* Debug */ = { 188 | isa = XCBuildConfiguration; 189 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; 190 | buildSettings = { 191 | ALWAYS_SEARCH_USER_PATHS = NO; 192 | CLANG_ANALYZER_NONNULL = YES; 193 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 194 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 195 | CLANG_CXX_LIBRARY = "libc++"; 196 | CLANG_ENABLE_MODULES = YES; 197 | CLANG_ENABLE_OBJC_ARC = YES; 198 | CLANG_ENABLE_OBJC_WEAK = YES; 199 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 200 | CLANG_WARN_BOOL_CONVERSION = YES; 201 | CLANG_WARN_COMMA = YES; 202 | CLANG_WARN_CONSTANT_CONVERSION = YES; 203 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 204 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 205 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 206 | CLANG_WARN_EMPTY_BODY = YES; 207 | CLANG_WARN_ENUM_CONVERSION = YES; 208 | CLANG_WARN_INFINITE_RECURSION = YES; 209 | CLANG_WARN_INT_CONVERSION = YES; 210 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 211 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 212 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 213 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 214 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 215 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 216 | CLANG_WARN_STRICT_PROTOTYPES = YES; 217 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 218 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 219 | CLANG_WARN_UNREACHABLE_CODE = YES; 220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 221 | COPY_PHASE_STRIP = NO; 222 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 223 | ENABLE_STRICT_OBJC_MSGSEND = YES; 224 | ENABLE_TESTABILITY = YES; 225 | GCC_C_LANGUAGE_STANDARD = gnu11; 226 | GCC_DYNAMIC_NO_PIC = NO; 227 | GCC_NO_COMMON_BLOCKS = YES; 228 | GCC_OPTIMIZATION_LEVEL = 0; 229 | GCC_PREPROCESSOR_DEFINITIONS = ( 230 | "DEBUG=1", 231 | "$(inherited)", 232 | ); 233 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 234 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 235 | GCC_WARN_UNDECLARED_SELECTOR = YES; 236 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 237 | GCC_WARN_UNUSED_FUNCTION = YES; 238 | GCC_WARN_UNUSED_VARIABLE = YES; 239 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 240 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 241 | MTL_FAST_MATH = YES; 242 | ONLY_ACTIVE_ARCH = YES; 243 | SDKROOT = iphoneos; 244 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 245 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 246 | }; 247 | name = Debug; 248 | }; 249 | 7555FFA4242A565B00829871 /* Release */ = { 250 | isa = XCBuildConfiguration; 251 | baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_NONNULL = YES; 255 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_ENABLE_OBJC_WEAK = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 268 | CLANG_WARN_EMPTY_BODY = YES; 269 | CLANG_WARN_ENUM_CONVERSION = YES; 270 | CLANG_WARN_INFINITE_RECURSION = YES; 271 | CLANG_WARN_INT_CONVERSION = YES; 272 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 274 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 285 | ENABLE_NS_ASSERTIONS = NO; 286 | ENABLE_STRICT_OBJC_MSGSEND = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu11; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 296 | MTL_ENABLE_DEBUG_INFO = NO; 297 | MTL_FAST_MATH = YES; 298 | SDKROOT = iphoneos; 299 | SWIFT_COMPILATION_MODE = wholemodule; 300 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Release; 304 | }; 305 | 7555FFA6242A565B00829871 /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | CODE_SIGN_IDENTITY = "Apple Development"; 310 | CODE_SIGN_STYLE = Automatic; 311 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 312 | DEVELOPMENT_TEAM = "${TEAM_ID}"; 313 | ENABLE_PREVIEWS = YES; 314 | FRAMEWORK_SEARCH_PATHS = ( 315 | "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", 316 | ); 317 | INFOPLIST_FILE = iosApp/Info.plist; 318 | INFOPLIST_KEY_CFBundleDisplayName = "Delivery App"; 319 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 320 | LD_RUNPATH_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "@executable_path/Frameworks", 323 | ); 324 | OTHER_LDFLAGS = ( 325 | "$(inherited)", 326 | "-framework", 327 | composeApp, 328 | ); 329 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; 330 | PRODUCT_NAME = "${APP_NAME}"; 331 | PROVISIONING_PROFILE_SPECIFIER = ""; 332 | SWIFT_VERSION = 5.0; 333 | TARGETED_DEVICE_FAMILY = "1,2"; 334 | }; 335 | name = Debug; 336 | }; 337 | 7555FFA7242A565B00829871 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | CODE_SIGN_IDENTITY = "Apple Development"; 342 | CODE_SIGN_STYLE = Automatic; 343 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 344 | DEVELOPMENT_TEAM = "${TEAM_ID}"; 345 | ENABLE_PREVIEWS = YES; 346 | FRAMEWORK_SEARCH_PATHS = ( 347 | "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", 348 | ); 349 | INFOPLIST_FILE = iosApp/Info.plist; 350 | INFOPLIST_KEY_CFBundleDisplayName = "Delivery App"; 351 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 352 | LD_RUNPATH_SEARCH_PATHS = ( 353 | "$(inherited)", 354 | "@executable_path/Frameworks", 355 | ); 356 | OTHER_LDFLAGS = ( 357 | "$(inherited)", 358 | "-framework", 359 | composeApp, 360 | ); 361 | PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; 362 | PRODUCT_NAME = "${APP_NAME}"; 363 | PROVISIONING_PROFILE_SPECIFIER = ""; 364 | SWIFT_VERSION = 5.0; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | 7555FFA3242A565B00829871 /* Debug */, 376 | 7555FFA4242A565B00829871 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | 7555FFA6242A565B00829871 /* Debug */, 385 | 7555FFA7242A565B00829871 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = 7555FF73242A565900829871 /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/files/vendors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 473067, 4 | "name": "Domino's Pizza - Punta Carretas", 5 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/logo-haprh3.jpg", 6 | "location": { 7 | "lat": -34.9195898, 8 | "lng": -56.15259409999999 9 | }, 10 | "isNew": false, 11 | "isExclusive": false, 12 | "deliveryTime": "20-35 min", 13 | "deliveryFee": 55, 14 | "rating": 4.18, 15 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/f915c6d9-6dbc-4d5d-9776-0523d5ef25af.jpg", 16 | "categories": [ 17 | "Pizzas" 18 | ] 19 | }, 20 | { 21 | "id": 53023, 22 | "name": "McDonald's Boulevard Y Palmar", 23 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/mcdonald-s-boulevard-y-palmar-logo.jpg", 24 | "location": { 25 | "lat": -34.9009782, 26 | "lng": -56.16391030000001 27 | }, 28 | "isNew": false, 29 | "isExclusive": false, 30 | "deliveryTime": "10-25 min", 31 | "deliveryFee": 49, 32 | "rating": 3.58, 33 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/mc-donalds-uruguay-header-custom-2020.jpg", 34 | "categories": [ 35 | "Hamburguesas" 36 | ] 37 | }, 38 | { 39 | "id": 29474, 40 | "name": "Bar 18", 41 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/bar-18-new.jpg", 42 | "location": { 43 | "lat": -34.89899826049805, 44 | "lng": -56.16880035400391 45 | }, 46 | "isNew": false, 47 | "isExclusive": false, 48 | "deliveryTime": "15-30 min", 49 | "deliveryFee": 29, 50 | "rating": 4.08, 51 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/bar-18-custom-header.jpg", 52 | "categories": [ 53 | "Milanesas", 54 | "Pizzas" 55 | ] 56 | }, 57 | { 58 | "id": 310729, 59 | "name": "Mostaza - Tres Cruces", 60 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/logo-mostaza-200x200px.jpg", 61 | "location": { 62 | "lat": -34.8944419, 63 | "lng": -56.1665392 64 | }, 65 | "isNew": false, 66 | "isExclusive": false, 67 | "deliveryTime": "10-25 min", 68 | "deliveryFee": 55, 69 | "rating": 3.98, 70 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/8c6a5319-6c80-4a5d-97c8-ed145b2c987a.jpg", 71 | "categories": [] 72 | }, 73 | { 74 | "id": 63445, 75 | "name": "Sbarro Tres Cruces", 76 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/4ovy4y.jpg", 77 | "location": { 78 | "lat": -34.89459991455078, 79 | "lng": -56.16659927368164 80 | }, 81 | "isNew": false, 82 | "isExclusive": true, 83 | "deliveryTime": "25-40 min", 84 | "deliveryFee": 55, 85 | "rating": 4.38, 86 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/1ac3b4b0-3017-479b-a7e3-7ad115ba3ed8.jpg", 87 | "categories": [ 88 | "Pizzas" 89 | ] 90 | }, 91 | { 92 | "id": 193348, 93 | "name": "El Club De La Papa Frita - 8 De Octubre", 94 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/el-club-de-la-papafrita-logo.jpg", 95 | "location": { 96 | "lat": -34.8893961, 97 | "lng": -56.1599718 98 | }, 99 | "isNew": false, 100 | "isExclusive": false, 101 | "deliveryTime": "15-30 min", 102 | "deliveryFee": 39, 103 | "rating": 4.41, 104 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/el-club-de-la-papa-frita-uy-header-custom.jpg", 105 | "categories": [ 106 | "Milanesas", 107 | "Pizzas" 108 | ] 109 | }, 110 | { 111 | "id": 471060, 112 | "name": "Domino's Pizza - Cordón", 113 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/hak3m4.jpg", 114 | "location": { 115 | "lat": -34.9078751, 116 | "lng": -56.1728506 117 | }, 118 | "isNew": false, 119 | "isExclusive": false, 120 | "deliveryTime": "10-25 min", 121 | "deliveryFee": 39, 122 | "rating": 4.06, 123 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/1cd8396a-e7cc-4572-bcdf-c0f81592a531.jpg", 124 | "categories": [ 125 | "Pizzas" 126 | ] 127 | }, 128 | { 129 | "id": 32143, 130 | "name": "La Fábrica - Cordón", 131 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/la-fabrica-logo.jpg", 132 | "location": { 133 | "lat": -34.90090179, 134 | "lng": -56.17819977 135 | }, 136 | "isNew": false, 137 | "isExclusive": false, 138 | "deliveryTime": "10-25 min", 139 | "deliveryFee": 39, 140 | "rating": 3.6, 141 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/la-fabrica-cordon-portada.jpg", 142 | "categories": [ 143 | "Milanesas" 144 | ] 145 | }, 146 | { 147 | "id": 54008, 148 | "name": "El Club De La Papa Frita - Parque Batlle", 149 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/logo-el-club-de-la-papafrita.jpg", 150 | "location": { 151 | "lat": -34.90240097045898, 152 | "lng": -56.16339874267578 153 | }, 154 | "isNew": false, 155 | "isExclusive": false, 156 | "deliveryTime": "15-30 min", 157 | "deliveryFee": 29, 158 | "rating": 4.1, 159 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/el-club-de-la-papa-frita-uy-header-custom.jpg", 160 | "categories": [ 161 | "Pizzas" 162 | ] 163 | }, 164 | { 165 | "id": 304902, 166 | "name": "Grido - Pocitos - 5274", 167 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/grido-uru-logo.jpg", 168 | "location": { 169 | "lat": -34.90710067749023, 170 | "lng": -56.16019821166992 171 | }, 172 | "isNew": false, 173 | "isExclusive": false, 174 | "deliveryTime": "5-20 min", 175 | "deliveryFee": 39, 176 | "rating": 4.42, 177 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/hwh1ai.jpg", 178 | "categories": [ 179 | "Helados" 180 | ] 181 | }, 182 | { 183 | "id": 345446, 184 | "name": "Kentucky - Pizza Argentina Parque Batlle", 185 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/logo-kentucky-pizza.jpg", 186 | "location": { 187 | "lat": -34.89879989624023, 188 | "lng": -56.16460037231445 189 | }, 190 | "isNew": false, 191 | "isExclusive": false, 192 | "deliveryTime": "10-25 min", 193 | "deliveryFee": 29, 194 | "rating": 3.81, 195 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/whatsapp-image-2022-03-14-at-10-14-15-am.jpeg", 196 | "categories": [ 197 | "Pizzas" 198 | ] 199 | }, 200 | { 201 | "id": 374594, 202 | "name": "El Club De La Papa Frita - Cordón", 203 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/logo-el-club-de-la-papafrita.jpg", 204 | "location": { 205 | "lat": -34.90231469999999, 206 | "lng": -56.1632864 207 | }, 208 | "isNew": false, 209 | "isExclusive": false, 210 | "deliveryTime": "10-25 min", 211 | "deliveryFee": 29, 212 | "rating": 3.94, 213 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/el-club-de-la-papa-frita-uy-header-custom.jpg", 214 | "categories": [ 215 | "Pizzas" 216 | ] 217 | }, 218 | { 219 | "id": 155584, 220 | "name": "Bar Castrobó", 221 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/bar-castrobo-logo1.jpg", 222 | "location": { 223 | "lat": -34.89920043945313, 224 | "lng": -56.16880035400391 225 | }, 226 | "isNew": false, 227 | "isExclusive": true, 228 | "deliveryTime": "10-25 min", 229 | "deliveryFee": 29, 230 | "rating": 4.7, 231 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/4ov0s5-headers-custom.jpg", 232 | "categories": [ 233 | "Milanesas" 234 | ] 235 | }, 236 | { 237 | "id": 279402, 238 | "name": "Lo De Pepe (comer A Lo Grande) - Tres Cruces", 239 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/lo-de-pepe-shopping-xxx.png", 240 | "location": { 241 | "lat": -34.89459991455078, 242 | "lng": -56.16659927368164 243 | }, 244 | "isNew": false, 245 | "isExclusive": false, 246 | "deliveryTime": "15-30 min", 247 | "deliveryFee": 29, 248 | "rating": 4.33, 249 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/29c5824d-7203-49f1-a518-3bfeb1bb67fc.jpg", 250 | "categories": [ 251 | "Milanesas", 252 | "Chivitos" 253 | ] 254 | }, 255 | { 256 | "id": 18199, 257 | "name": "La Marañada", 258 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/la-maranada-new.jpg", 259 | "location": { 260 | "lat": -34.90250015258789, 261 | "lng": -56.16899871826172 262 | }, 263 | "isNew": false, 264 | "isExclusive": true, 265 | "deliveryTime": "10-25 min", 266 | "deliveryFee": 29, 267 | "rating": 4.5, 268 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/la-maranada-custom-header.png", 269 | "categories": [ 270 | "Milanesas", 271 | "Carnes" 272 | ] 273 | }, 274 | { 275 | "id": 175086, 276 | "name": "Ola Poke Mercado Ferrando", 277 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/ola-poke-logo-001.png", 278 | "location": { 279 | "lat": -34.90470123291016, 280 | "lng": -56.1702995300293 281 | }, 282 | "isNew": false, 283 | "isExclusive": true, 284 | "deliveryTime": "15-30 min", 285 | "deliveryFee": 29, 286 | "rating": 4.16, 287 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/hkf9r5.jpg", 288 | "categories": [ 289 | "Poke" 290 | ] 291 | }, 292 | { 293 | "id": 453486, 294 | "name": "Viva La Mila - Pocitos", 295 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/viva-la-mila-pocitos-bartolito-mitre-2848-57e7045b-8756-4f2e-ad43-c099321115e1.jpg", 296 | "location": { 297 | "lat": -34.9069448, 298 | "lng": -56.15176285 299 | }, 300 | "isNew": false, 301 | "isExclusive": false, 302 | "deliveryTime": "15-30 min", 303 | "deliveryFee": 39, 304 | "rating": 4.3, 305 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/35d8d762-1703-4553-962f-bba6bc4ba32e.jpg", 306 | "categories": [ 307 | "Pizzas", 308 | "Milanesas" 309 | ] 310 | }, 311 | { 312 | "id": 477548, 313 | "name": "Kamisushi Pocitos", 314 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/kami-sushi-la-union-04afbd81-74d4-4bcf-99ba-04689e7f323d.jpg", 315 | "location": { 316 | "lat": -34.9029671, 317 | "lng": -56.1516756 318 | }, 319 | "isNew": false, 320 | "isExclusive": false, 321 | "deliveryTime": "20-35 min", 322 | "deliveryFee": 39, 323 | "rating": 4.43, 324 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/3f0c8bac-4f9b-4f89-98ef-e77605883dc1.jpg", 325 | "categories": [ 326 | "Sushi" 327 | ] 328 | }, 329 | { 330 | "id": 37498, 331 | "name": "Despacho De Pizzetas", 332 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/despacho-pizza-logo.jpg", 333 | "location": { 334 | "lat": -34.89910125732422, 335 | "lng": -56.16889953613281 336 | }, 337 | "isNew": false, 338 | "isExclusive": false, 339 | "deliveryTime": "15-30 min", 340 | "deliveryFee": 29, 341 | "rating": 3.69, 342 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/despacho-pizzetas-headers-custom.jpg", 343 | "categories": [ 344 | "Pizzas" 345 | ] 346 | }, 347 | { 348 | "id": 142385, 349 | "name": "Hamburgueseria Magico", 350 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/5c0d76a4-3dbe-4ae0-be00-307f57058783.jpg", 351 | "location": { 352 | "lat": -34.8927001953125, 353 | "lng": -56.16650009155273 354 | }, 355 | "isNew": false, 356 | "isExclusive": false, 357 | "deliveryTime": "10-25 min", 358 | "deliveryFee": 39, 359 | "rating": 4.46, 360 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/52393a64-e360-4107-8346-695a99a6d912.jpg", 361 | "categories": [ 362 | "Hamburguesas" 363 | ] 364 | }, 365 | { 366 | "id": 162812, 367 | "name": "La Terna", 368 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/whatsapp-image-2020-07-04-at-3-55-13-pm.jpg", 369 | "location": { 370 | "lat": -34.88999938964844, 371 | "lng": -56.16669845581055 372 | }, 373 | "isNew": false, 374 | "isExclusive": false, 375 | "deliveryTime": "15-30 min", 376 | "deliveryFee": 39, 377 | "rating": 4.6, 378 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/hkumdr-headers-custom.jpg", 379 | "categories": [ 380 | "Pizzas", 381 | "Carnes" 382 | ] 383 | }, 384 | { 385 | "id": 119541, 386 | "name": "Bar Micons", 387 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/hkhcuy.jpg", 388 | "location": { 389 | "lat": -34.8924295, 390 | "lng": -56.17538780000001 391 | }, 392 | "isNew": false, 393 | "isExclusive": false, 394 | "deliveryTime": "15-30 min", 395 | "deliveryFee": 39, 396 | "rating": 4.73, 397 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/a8629ca1-3e1b-44c4-949d-d34763f7a81b.jpg", 398 | "categories": [ 399 | "Milanesas" 400 | ] 401 | }, 402 | { 403 | "id": 18298, 404 | "name": "Bar Pizzería Luz", 405 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/barluz-logo.jpg", 406 | "location": { 407 | "lat": -34.902099609375, 408 | "lng": -56.1692008972168 409 | }, 410 | "isNew": false, 411 | "isExclusive": true, 412 | "deliveryTime": "10-25 min", 413 | "deliveryFee": 29, 414 | "rating": 4.42, 415 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/bar-pizzeria-luz-custom-header.jpg", 416 | "categories": [ 417 | "Pizzas" 418 | ] 419 | }, 420 | { 421 | "id": 7825, 422 | "name": "Bar Rodó", 423 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/bar-rodo-express.jpg", 424 | "location": { 425 | "lat": -34.91180038452148, 426 | "lng": -56.17169952392578 427 | }, 428 | "isNew": false, 429 | "isExclusive": false, 430 | "deliveryTime": "10-25 min", 431 | "deliveryFee": 39, 432 | "rating": 4.66, 433 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/7825.jpg", 434 | "categories": [ 435 | "Pizzas" 436 | ] 437 | }, 438 | { 439 | "id": 383705, 440 | "name": "West", 441 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/west.jpg", 442 | "location": { 443 | "lat": -34.9027373, 444 | "lng": -56.1754981 445 | }, 446 | "isNew": false, 447 | "isExclusive": false, 448 | "deliveryTime": "25-40 min", 449 | "deliveryFee": 39, 450 | "rating": 4.51, 451 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/custom-header-west.jpg", 452 | "categories": [ 453 | "Hamburguesas" 454 | ] 455 | }, 456 | { 457 | "id": 45786, 458 | "name": "Palacio Pizza Café", 459 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/palacio-cafe.jpg", 460 | "location": { 461 | "lat": -34.8908731, 462 | "lng": -56.1562191 463 | }, 464 | "isNew": false, 465 | "isExclusive": false, 466 | "deliveryTime": "15-30 min", 467 | "deliveryFee": 39, 468 | "rating": 4.35, 469 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/palacio-pizza-cafe-custom-header.jpg", 470 | "categories": [ 471 | "Pizzas" 472 | ] 473 | }, 474 | { 475 | "id": 221595, 476 | "name": "Remix Salads", 477 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/remix-salads.jpg", 478 | "location": { 479 | "lat": -34.9019856, 480 | "lng": -56.17939209999999 481 | }, 482 | "isNew": false, 483 | "isExclusive": false, 484 | "deliveryTime": "20-35 min", 485 | "deliveryFee": 39, 486 | "rating": 4.19, 487 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/221595.jpg", 488 | "categories": [ 489 | "Ensaladas" 490 | ] 491 | }, 492 | { 493 | "id": 179936, 494 | "name": "Mundo Mila - Buceo", 495 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/hk713t.jpg", 496 | "location": { 497 | "lat": -34.9031283, 498 | "lng": -56.159353 499 | }, 500 | "isNew": false, 501 | "isExclusive": false, 502 | "deliveryTime": "20-35 min", 503 | "deliveryFee": 29, 504 | "rating": 4.07, 505 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/profile-headers/3713cb09-73fa-4713-9d2e-a4ba68298604.jpg", 506 | "categories": [ 507 | "Pizzas", 508 | "Milanesas" 509 | ] 510 | }, 511 | { 512 | "id": 29396, 513 | "name": "Burgers Pocitos", 514 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/burgersv2alogo.jpg", 515 | "location": { 516 | "lat": -34.90807459920502, 517 | "lng": -56.17125549999999 518 | }, 519 | "isNew": false, 520 | "isExclusive": false, 521 | "deliveryTime": "15-30 min", 522 | "deliveryFee": 39, 523 | "rating": 4.47, 524 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/foto-de-portada-del-perfil-portada.jpg", 525 | "categories": [ 526 | "Comida Vegetariana", 527 | "Hamburguesas" 528 | ] 529 | }, 530 | { 531 | "id": 435160, 532 | "name": "Soprano´s Pocitos", 533 | "logo": "https://images.deliveryhero.io/image/pedidosya/restaurants/sopranos-pocitos-2780725e-e7f8-4803-92e8-835b3e4abee1.png", 534 | "location": { 535 | "lat": -34.91199238111003, 536 | "lng": -56.16053905487436 537 | }, 538 | "isNew": false, 539 | "isExclusive": false, 540 | "deliveryTime": "10-25 min", 541 | "deliveryFee": 39, 542 | "rating": 4.39, 543 | "headerImage": "https://images.deliveryhero.io/image/pedidosya/profile-headers/a9d62a5b-d059-4f33-a9cf-f2def284fdf3.jpg", 544 | "categories": [ 545 | "Pizzas" 546 | ] 547 | } 548 | ] --------------------------------------------------------------------------------