├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── preloaded_fonts.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── font_certs.xml
│ │ │ ├── drawable
│ │ │ │ ├── bg_map.png
│ │ │ │ ├── img_bar.png
│ │ │ │ ├── img_brunch.png
│ │ │ │ ├── img_burger.png
│ │ │ │ ├── img_coffee.png
│ │ │ │ ├── img_steak.png
│ │ │ │ ├── img_sushi.png
│ │ │ │ ├── ic_arrow_left.xml
│ │ │ │ ├── ic_user_location.xml
│ │ │ │ ├── ic_movie.xml
│ │ │ │ ├── ic_phone.xml
│ │ │ │ ├── ic_tools_kitchen_2.xml
│ │ │ │ ├── ic_bed.xml
│ │ │ │ ├── ic_gas_station.xml
│ │ │ │ ├── ic_first_aid_kit.xml
│ │ │ │ ├── ic_scan.xml
│ │ │ │ ├── img_pin.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── ic_shopping_cart.xml
│ │ │ │ ├── ic_map_pin.xml
│ │ │ │ ├── ic_ticket.xml
│ │ │ │ ├── ic_shopping_bag.xml
│ │ │ │ ├── img_loader.xml
│ │ │ │ ├── ic_qrcode.xml
│ │ │ │ ├── ic_nearby_launcher_foreground.xml
│ │ │ │ ├── img_logo_logo_icon.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── ic_bakery.xml
│ │ │ │ └── bg_splash_screen.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_nearby_launcher.webp
│ │ │ │ ├── ic_launcher_foreground.webp
│ │ │ │ └── ic_nearby_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_nearby_launcher.webp
│ │ │ │ ├── ic_launcher_foreground.webp
│ │ │ │ └── ic_nearby_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_nearby_launcher.webp
│ │ │ │ ├── ic_launcher_foreground.webp
│ │ │ │ └── ic_nearby_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_nearby_launcher.webp
│ │ │ │ ├── ic_launcher_foreground.webp
│ │ │ │ └── ic_nearby_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_nearby_launcher.webp
│ │ │ │ ├── ic_launcher_foreground.webp
│ │ │ │ └── ic_nearby_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_nearby_launcher.xml
│ │ │ │ └── ic_nearby_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ ├── network_security_config.xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── font
│ │ │ │ ├── rubik.xml
│ │ │ │ ├── rubik_bold.xml
│ │ │ │ ├── rubik_medium.xml
│ │ │ │ └── rubik_semibold.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── rocketseat
│ │ │ │ └── nlw
│ │ │ │ └── nearby
│ │ │ │ ├── data
│ │ │ │ └── model
│ │ │ │ │ ├── Coupon.kt
│ │ │ │ │ ├── mock
│ │ │ │ │ ├── MockUserLocation.kt
│ │ │ │ │ ├── MockCategories.kt
│ │ │ │ │ ├── MockRules.kt
│ │ │ │ │ └── MockMarkets.kt
│ │ │ │ │ ├── Rule.kt
│ │ │ │ │ ├── Market.kt
│ │ │ │ │ ├── Category.kt
│ │ │ │ │ └── MarketDetails.kt
│ │ │ │ ├── ui
│ │ │ │ ├── screen
│ │ │ │ │ ├── home
│ │ │ │ │ │ ├── HomeUiEvent.kt
│ │ │ │ │ │ ├── HomeUiState.kt
│ │ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ │ └── HomeScreen.kt
│ │ │ │ │ ├── market_details
│ │ │ │ │ │ ├── MarketDetailsUiState.kt
│ │ │ │ │ │ ├── MarketDetailsUiEvent.kt
│ │ │ │ │ │ ├── MarketDetailsViewModel.kt
│ │ │ │ │ │ └── MarketDetailsScreen.kt
│ │ │ │ │ ├── splash
│ │ │ │ │ │ └── SplashScreen.kt
│ │ │ │ │ ├── welcome
│ │ │ │ │ │ └── WelcomeScreen.kt
│ │ │ │ │ └── qrcode_scanner
│ │ │ │ │ │ └── QRCodeScannerScreen.kt
│ │ │ │ ├── route
│ │ │ │ │ └── UIRoutes.kt
│ │ │ │ ├── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Type.kt
│ │ │ │ ├── util
│ │ │ │ │ └── MapUtils.kt
│ │ │ │ └── component
│ │ │ │ │ ├── category
│ │ │ │ │ ├── CategoryFilterChipView.kt
│ │ │ │ │ ├── NearbyCategoryFilterChipList.kt
│ │ │ │ │ └── NearbyCategoryFilterChip.kt
│ │ │ │ │ ├── welcome
│ │ │ │ │ ├── WelcomeHeader.kt
│ │ │ │ │ ├── WelcomeContent.kt
│ │ │ │ │ └── WelcomeHowItWorksTip.kt
│ │ │ │ │ ├── market
│ │ │ │ │ ├── NearbyMarketCardList.kt
│ │ │ │ │ └── NearbyMarketCard.kt
│ │ │ │ │ ├── market_details
│ │ │ │ │ ├── NearbyMarketDetailsRules.kt
│ │ │ │ │ ├── NearbyMarketDetailsCoupons.kt
│ │ │ │ │ └── NearbyMarketDetailsInfos.kt
│ │ │ │ │ ├── button
│ │ │ │ │ └── NearbyButton.kt
│ │ │ │ │ └── home
│ │ │ │ │ └── NearbyGoogleMap.kt
│ │ │ │ ├── core
│ │ │ │ └── network
│ │ │ │ │ ├── KtorHttpClient.kt
│ │ │ │ │ └── NearbyRemoteDataSource.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── rocketseat
│ │ │ └── nlw
│ │ │ └── nearby
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── rocketseat
│ │ └── nlw
│ │ └── nearby
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── .idea
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── migrations.xml
├── deviceManager.xml
├── misc.xml
├── gradle.xml
├── deploymentTargetSelector.xml
├── runConfigurations.xml
├── appInsightsSettings.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── settings.gradle.kts
├── .kotlin
└── errors
│ └── errors-1732482169599.log
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Nearby
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/bg_map.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_bar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_brunch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_brunch.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_burger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_burger.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_coffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_coffee.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_steak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_steak.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_sushi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/drawable/img_sushi.png
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #257F49
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_nearby_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-hdpi/ic_nearby_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_nearby_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-mdpi/ic_nearby_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_nearby_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xhdpi/ic_nearby_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_nearby_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxhdpi/ic_nearby_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_nearby_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_nearby_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_nearby_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-hdpi/ic_nearby_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_nearby_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-mdpi/ic_nearby_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_nearby_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xhdpi/ic_nearby_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_nearby_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxhdpi/ic_nearby_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_nearby_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-education/nlw-pocket-mobile-kotlin/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_nearby_launcher_round.webp
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/Coupon.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Coupon(
7 | val coupon: String,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/mock/MockUserLocation.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model.mock
2 |
3 | import com.google.android.gms.maps.model.LatLng
4 |
5 | val mockUserLocation = LatLng(
6 | -23.561187293883442,
7 | -46.656451388116494
8 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/home/HomeUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.home
2 |
3 | sealed class HomeUiEvent {
4 | data object OnFetchCategories : HomeUiEvent()
5 | data class OnFetchMarkets(val categoryId: String) : HomeUiEvent()
6 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 12 19:38:34 BRT 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/Rule.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Rule(
7 | val id: String,
8 | val description: String,
9 | val marketId: String
10 | )
11 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/market_details/MarketDetailsUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.market_details
2 |
3 | import com.rocketseat.nlw.nearby.data.model.Rule
4 |
5 | data class MarketDetailsUiState(
6 | val rules: List? = null,
7 | val coupon: String? = null,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_nearby_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_nearby_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/route/UIRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.route
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data object Splash
7 |
8 | @Serializable
9 | data object Welcome
10 |
11 | @Serializable
12 | data object Home
13 |
14 | @Serializable
15 | data object QRCodeScanner
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SEU_SUB_DOMINIO_AQUI
5 | 10.0.2.2
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/preloaded_fonts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @font/rubik
5 | - @font/rubik_bold
6 | - @font/rubik_medium
7 | - @font/rubik_semibold
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/font/rubik.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/market_details/MarketDetailsUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.market_details
2 |
3 | sealed class MarketDetailsUiEvent {
4 | data class OnFetchRules(val marketId: String) : MarketDetailsUiEvent()
5 | data class OnFetchCoupon(val qrCodeContent: String) : MarketDetailsUiEvent()
6 | data object OnResetCoupon : MarketDetailsUiEvent()
7 | }
--------------------------------------------------------------------------------
/.idea/deviceManager.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/font/rubik_bold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/rubik_medium.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/font/rubik_semibold.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/home/HomeUiState.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.home
2 |
3 | import com.google.android.gms.maps.model.LatLng
4 | import com.rocketseat.nlw.nearby.data.model.Category
5 | import com.rocketseat.nlw.nearby.data.model.Market
6 |
7 | data class HomeUiState(
8 | val categories: List? = null,
9 | val markets: List? = null,
10 | val marketLocations: List? = null
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/rocketseat/nlw/nearby/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/Market.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Market(
7 | val id: String,
8 | val categoryId: String,
9 | val name: String,
10 | val description: String,
11 | val coupons: Int,
12 | val latitude: Double,
13 | val longitude: Double,
14 | val address: String,
15 | val phone: String,
16 | val cover: String,
17 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/Category.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model
2 |
3 | import androidx.annotation.DrawableRes
4 | import com.rocketseat.nlw.nearby.ui.component.category.CategoryFilterChipView
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class Category(
9 | val id: String,
10 | val name: String
11 | ) {
12 | @get:DrawableRes
13 | val icon: Int?
14 | get() = CategoryFilterChipView.fromDescription(description = name)?.icon
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/MarketDetails.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class MarketDetails(
7 | val id: String,
8 | val categoryId: String,
9 | val name: String,
10 | val description: String,
11 | val rules: List,
12 | val coupons: Int,
13 | val latitude: Double,
14 | val longitude: Double,
15 | val address: String,
16 | val phone: String,
17 | val cover: String,
18 | )
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/mock/MockCategories.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model.mock
2 |
3 | import com.rocketseat.nlw.nearby.data.model.Category
4 |
5 | val mockCategories = listOf(
6 | Category(
7 | id = "1",
8 | name = "Alimentação"
9 | ),
10 | Category(
11 | id = "2",
12 | name = "Cinema"
13 | ),
14 | Category(
15 | id = "3",
16 | name = "Farmácia"
17 | ),
18 | Category(
19 | id = "4",
20 | name = "Supermercado"
21 | )
22 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val GreenLight = Color(0xFF3B9B62)
6 | val GreenBase = Color(0xFF257F49)
7 | val GreenDark = Color(0xFF052914)
8 | val GreenExtraLight = Color(0xFFE9F3EF)
9 |
10 | val RedLight = Color(0xFFFDEDED)
11 | val RedBase = Color(0xFFF94144)
12 |
13 | val Gray100 = Color(0xFFFCFDFE)
14 | val Gray200 = Color(0xFFE1EBF4)
15 | val Gray300 = Color(0xFFC4D0DB)
16 | val Gray400 = Color(0xFF73808C)
17 | val Gray500 = Color(0xFF45525F)
18 | val Gray600 = Color(0xFF1A1F24)
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/mock/MockRules.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model.mock
2 |
3 | import com.rocketseat.nlw.nearby.data.model.Rule
4 |
5 | val mockRules = listOf(
6 | Rule(
7 | id = "012576ea-4441-4b8a-89e5-d5f32104c7c4",
8 | description = "Disponível até 31/12/2024",
9 | marketId = "012576ea-4441-4b8a-89e5-d5f32104c7c4"
10 | ),
11 | Rule(
12 | id = "312576ea-4341-1b8a-83e5-d5f32204c7b1",
13 | description = "Válido apenas para consumo no local",
14 | marketId = "bde73364-95c5-46e4-8084-79a7ca3824c4"
15 | )
16 | )
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "Nearby"
23 | include(":app")
24 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/.kotlin/errors/errors-1732482169599.log:
--------------------------------------------------------------------------------
1 | kotlin version: 2.0.20
2 | error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
3 | 1. Kotlin compile daemon is ready
4 |
5 | error message: The daemon has terminated unexpectedly on startup attempt #2 with error code: 0. The daemon process output:
6 | 1. Kotlin compile daemon is ready
7 | Problems may have occurred during auto-selection of GC. The preferred GC is Parallel GC.
8 | If the problems persist, try adding the JVM option to the Kotlin daemon JVM arguments: -XX:-UseParallelGC.
9 | GC auto-selection logic is disabled temporary for the next daemon startup.
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_user_location.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/rocketseat/nlw/nearby/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.rocketseat.nlw.nearby", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/appInsightsSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/util/MapUtils.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.util
2 |
3 | import com.google.android.gms.maps.model.LatLng
4 |
5 | fun findSouthwestPoint(points: List): LatLng {
6 | if(points.isEmpty()) return LatLng(0.0, 0.0)
7 |
8 | var southwestPoint = points[0]
9 |
10 | for(point in points) {
11 | if (point.latitude < southwestPoint.latitude ||
12 | (point.latitude == southwestPoint.latitude && point.latitude < southwestPoint.latitude)
13 | ) {
14 | southwestPoint = point
15 | }
16 | }
17 |
18 | return southwestPoint
19 | }
20 |
21 | fun findNortheastPoint(points: List): LatLng {
22 | if(points.isEmpty()) return LatLng(0.0, 0.0)
23 |
24 | var northeastPoint = points[0]
25 |
26 | for(point in points) {
27 | if (point.latitude > northeastPoint.latitude ||
28 | (point.latitude == northeastPoint.latitude && point.latitude > northeastPoint.latitude)
29 | ) {
30 | northeastPoint = point
31 | }
32 | }
33 |
34 | return northeastPoint
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/category/CategoryFilterChipView.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.category
2 |
3 | import androidx.annotation.DrawableRes
4 | import com.rocketseat.nlw.nearby.R
5 |
6 | enum class CategoryFilterChipView(
7 | val description: String,
8 | @DrawableRes val icon: Int
9 | ) {
10 | ALIMENTACAO(description = "Alimentação", icon = R.drawable.ic_tools_kitchen_2),
11 | COMPRAS(description = "Compras", icon = R.drawable.ic_shopping_bag),
12 | HOSPEDAGEM(description = "Hospedagem", icon = R.drawable.ic_bed),
13 | SUPERMERCADO(description = "Supermercado", icon = R.drawable.ic_shopping_cart),
14 | ENTRETENIMENTO(description = "Cinema", icon = R.drawable.ic_movie),
15 | FARMACIA(description = "Farmácia", icon = R.drawable.ic_first_aid_kit),
16 | COMBUSTIVEL(description = "Combustível", icon = R.drawable.ic_gas_station),
17 | PADARIA(description = "Padaria", icon = R.drawable.ic_bakery);
18 |
19 | companion object {
20 | fun fromDescription(description: String): CategoryFilterChipView? {
21 | return entries.find { it.description == description }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_movie.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_phone.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/welcome/WelcomeHeader.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.welcome
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.unit.dp
13 | import com.rocketseat.nlw.nearby.R
14 | import com.rocketseat.nlw.nearby.ui.theme.Typography
15 |
16 | @Composable
17 | fun WelcomeHeader(modifier: Modifier = Modifier) {
18 | Column(
19 | modifier = modifier,
20 | verticalArrangement = Arrangement.spacedBy(16.dp)
21 | ) {
22 | Image(
23 | painter = painterResource(id = R.drawable.img_logo_logo_icon),
24 | contentDescription = "Nearby App Logo"
25 | )
26 | Spacer(modifier = Modifier.height(24.dp))
27 | Text(text = "Boas vindas ao Nearby", style = Typography.headlineLarge)
28 | Text(
29 | text = "Tenha cupons de vantagem para usar em seus estabelecimentos favoritos.",
30 | style = Typography.bodyLarge
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tools_kitchen_2.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bed.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.theme
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.darkColorScheme
5 | import androidx.compose.material3.lightColorScheme
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.graphics.Color
8 |
9 | private val LightColors = lightColorScheme(
10 | primary = GreenBase,
11 | onPrimary = Color.White,
12 | secondary = RedBase,
13 | onSecondary = Color.White,
14 | background = Gray100,
15 | onBackground = Gray600,
16 | surface = Gray100,
17 | onSurface = Gray600,
18 | error = RedBase,
19 | onError = Color.White
20 | )
21 |
22 | private val DarkColors = darkColorScheme(
23 | primary = GreenLight,
24 | onPrimary = Color.Black,
25 | secondary = RedLight,
26 | onSecondary = Color.Black,
27 | background = Gray600,
28 | onBackground = Gray100,
29 | surface = Gray600,
30 | onSurface = Gray200,
31 | error = RedBase,
32 | onError = Color.Black
33 | )
34 |
35 | @Composable
36 | fun NearbyTheme(
37 | darkTheme: Boolean = false,
38 | content: @Composable () -> Unit
39 | ) {
40 | val colors = if (darkTheme) {
41 | DarkColors
42 | } else {
43 | LightColors
44 | }
45 |
46 | MaterialTheme(
47 | colorScheme = colors,
48 | typography = Typography,
49 | content = content
50 | )
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/core/network/KtorHttpClient.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.core.network
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.engine.android.Android
5 | import io.ktor.client.plugins.HttpTimeout
6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
7 | import io.ktor.client.plugins.logging.LogLevel
8 | import io.ktor.client.plugins.logging.Logging
9 | import io.ktor.serialization.kotlinx.json.json
10 | import kotlinx.serialization.json.Json
11 |
12 | object KtorHttpClient {
13 | private const val NETWORK_TIMEOUT = 5_000L
14 |
15 | val httpClientAndroid by lazy {
16 | HttpClient(Android) {
17 | install(ContentNegotiation) {
18 | json(
19 | Json {
20 | prettyPrint = true
21 | isLenient = true
22 | useAlternativeNames = true
23 | ignoreUnknownKeys = true
24 | explicitNulls = true
25 | useArrayPolymorphism = true
26 | encodeDefaults = false
27 | }
28 | )
29 | }
30 |
31 | install(HttpTimeout) {
32 | requestTimeoutMillis = NETWORK_TIMEOUT
33 | connectTimeoutMillis = NETWORK_TIMEOUT
34 | socketTimeoutMillis = NETWORK_TIMEOUT
35 | }
36 |
37 | install(Logging) {
38 | level = LogLevel.ALL
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_gas_station.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/market/NearbyMarketCardList.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.market
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.items
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.tooling.preview.Preview
10 | import androidx.compose.ui.unit.dp
11 | import com.rocketseat.nlw.nearby.data.model.Market
12 | import com.rocketseat.nlw.nearby.data.model.mock.mockMarkets
13 | import com.rocketseat.nlw.nearby.ui.theme.Typography
14 |
15 |
16 | @Composable
17 | fun NearbyMarketCardList(
18 | modifier: Modifier = Modifier,
19 | markets: List,
20 | onMarketClick: (Market) -> Unit
21 | ) {
22 | LazyColumn(
23 | modifier = modifier,
24 | verticalArrangement = Arrangement.spacedBy(16.dp)
25 | ) {
26 | item {
27 | Text(text = "Explore locais perto de você", style = Typography.bodyLarge)
28 | }
29 | items(items = markets, key = { it.id }) { market ->
30 | NearbyMarketCard(
31 | market = market,
32 | onClick = {
33 | onMarketClick(market)
34 | }
35 | )
36 | }
37 | }
38 | }
39 |
40 | @Preview
41 | @Composable
42 | private fun NearbyMarketCardListPreview() {
43 | NearbyMarketCardList(
44 | markets = mockMarkets,
45 | onMarketClick = {}
46 | )
47 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_first_aid_kit.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/splash/SplashScreen.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.splash
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.LaunchedEffect
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import com.rocketseat.nlw.nearby.R
14 | import com.rocketseat.nlw.nearby.ui.theme.GreenLight
15 | import kotlinx.coroutines.delay
16 |
17 | @Composable
18 | fun SplashScreen(modifier: Modifier = Modifier, onNavigateToWelcome: () -> Unit) {
19 | LaunchedEffect(key1 = Unit) {
20 | delay(3_000)
21 | onNavigateToWelcome()
22 | }
23 |
24 | Box(
25 | modifier = modifier
26 | .background(GreenLight)
27 | .fillMaxSize(),
28 | ) {
29 | Image(
30 | modifier = Modifier.align(Alignment.Center),
31 | painter = painterResource(id = R.drawable.img_logo_logo_logo_text),
32 | contentDescription = "Imagem Logo"
33 | )
34 | Image(
35 | modifier = Modifier.align(Alignment.BottomCenter),
36 | painter = painterResource(id = R.drawable.bg_splash_screen),
37 | contentDescription = "Imagem Background"
38 | )
39 | }
40 | }
41 |
42 | @Preview
43 | @Composable
44 | private fun SplashScreenPreview() {
45 | SplashScreen(onNavigateToWelcome = {})
46 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_scan.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/welcome/WelcomeContent.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.welcome
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import com.rocketseat.nlw.nearby.R
11 | import com.rocketseat.nlw.nearby.ui.theme.Typography
12 |
13 | @Composable
14 | fun WelcomeContent(modifier: Modifier = Modifier) {
15 | Column(
16 | modifier = modifier,
17 | verticalArrangement = Arrangement.spacedBy(24.dp)
18 | ) {
19 | Text(text = "Veja como funciona:", style = Typography.bodyLarge)
20 | WelcomeHowItWorksTip(
21 | modifier = Modifier.fillMaxWidth(),
22 | title = "Encontre estabelecimentos",
23 | subtitle = "Veja locais perto de você que são parceiros Nearby",
24 | iconRes = R.drawable.ic_map_pin
25 | )
26 | WelcomeHowItWorksTip(
27 | modifier = Modifier.fillMaxWidth(),
28 | title = "Ative o cupom com QR Code",
29 | subtitle = "Escaneie o código no estabelecimento para usar o benefício",
30 | iconRes = R.drawable.ic_qrcode
31 | )
32 | WelcomeHowItWorksTip(
33 | modifier = Modifier.fillMaxWidth(),
34 | title = "Garanta vantagens perto de você",
35 | subtitle = "Ative cupons onde estiver, em diferentes tipos de estabelecimentos",
36 | iconRes = R.drawable.ic_ticket
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/data/model/mock/MockMarkets.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.data.model.mock
2 |
3 | import com.rocketseat.nlw.nearby.data.model.Market
4 |
5 | val mockMarkets = listOf(
6 | Market(
7 | id = "012576ea-4441-4b8a-89e5-d5f32104c7c4",
8 | categoryId = "146b1a88-b3d3-4232-8b8f-c1f006f1e86d",
9 | name = "Sabor Grill",
10 | description = "Churrascaria com cortes nobres e buffet variado. Experiência completa para os amantes de carne.",
11 | coupons = 10,
12 | // rules = listOf(
13 | // Rule(id = "1", description = "Válido até o dia 25/12", marketId = "012576ea-4441-4b8a-89e5-d5f32104c7c4"),
14 | // Rule(id = "2", description = "Disponível apenas para consumo local", marketId = "012576ea-4441-4b8a-89e5-d5f32104c7c4")
15 | // ),
16 | latitude = -23.55974230991911,
17 | longitude = -46.65814845249887,
18 | address = "Av. Paulista - Bela Vista",
19 | phone = "(11) 94567-1212",
20 | cover = "https://images.unsplash.com/photo-1498654896293-37aacf113fd9?w=400&h=300"
21 | ),
22 | Market(
23 | id = "2bc11e34-5f30-4ba0-90fa-c1c98f649281",
24 | categoryId = "146b1a88-b3d3-4232-8b8f-c1f006f1e86d",
25 | name = "Café Central",
26 | description = "Café aconchegante com opções de lanches e bebidas artesanais. Perfeito para uma pausa.",
27 | coupons = 10,
28 | // rules = emptyList(),
29 | latitude = -23.559457108504436,
30 | longitude = -46.66252581753144,
31 | address = "Alameda Jaú - Jardim Paulista",
32 | phone = "(12) 3456-7890",
33 | cover = "https://images.unsplash.com/photo-1551218808-94e220e084d2?w=400&h=300"
34 | )
35 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/market_details/NearbyMarketDetailsRules.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.market_details
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import androidx.compose.ui.unit.dp
12 | import androidx.compose.ui.unit.sp
13 | import com.rocketseat.nlw.nearby.data.model.Rule
14 | import com.rocketseat.nlw.nearby.data.model.mock.mockRules
15 | import com.rocketseat.nlw.nearby.ui.theme.Gray400
16 | import com.rocketseat.nlw.nearby.ui.theme.Gray500
17 | import com.rocketseat.nlw.nearby.ui.theme.Typography
18 |
19 | @Composable
20 | fun NearbyMarketDetailsRules(modifier: Modifier = Modifier, rules: List) {
21 | Column(
22 | modifier = modifier,
23 | verticalArrangement = Arrangement.spacedBy(16.dp)
24 | ) {
25 | Text(text = "Regulamento", style = Typography.headlineSmall, color = Gray400)
26 |
27 | Text(
28 | modifier = Modifier.padding(start = 16.dp),
29 | text = rules.joinToString(separator = "\n", transform = { "• ${it.description}" }),
30 | style = Typography.labelMedium,
31 | lineHeight = 24.sp,
32 | color = Gray500
33 | )
34 | }
35 | }
36 |
37 | @Preview
38 | @Composable
39 | private fun MarketDetailsRulesPreview() {
40 | NearbyMarketDetailsRules(
41 | modifier = Modifier.fillMaxWidth(),
42 | rules = mockRules
43 | )
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/welcome/WelcomeHowItWorksTip.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.welcome
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.unit.dp
16 | import com.rocketseat.nlw.nearby.ui.theme.Gray500
17 | import com.rocketseat.nlw.nearby.ui.theme.RedBase
18 | import com.rocketseat.nlw.nearby.ui.theme.Typography
19 |
20 | @Composable
21 | fun WelcomeHowItWorksTip(
22 | modifier: Modifier = Modifier,
23 | title: String,
24 | subtitle: String,
25 | @DrawableRes iconRes: Int
26 | ) {
27 | Row(
28 | modifier = modifier, horizontalArrangement = Arrangement.spacedBy(16.dp)
29 | ) {
30 | Icon(
31 | modifier = Modifier.size(32.dp),
32 | painter = painterResource(id = iconRes),
33 | tint = RedBase,
34 | contentDescription = "Ícone Como Funciona"
35 | )
36 | Spacer(modifier = Modifier.height(8.dp))
37 | Column(
38 | verticalArrangement = Arrangement.spacedBy(8.dp)
39 | ) {
40 | Text(text = title, style = Typography.headlineSmall)
41 | Text(text = subtitle, color = Gray500, style = Typography.bodyLarge)
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_pin.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/welcome/WelcomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.welcome
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.rememberScrollState
10 | import androidx.compose.foundation.verticalScroll
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.tooling.preview.Preview
15 | import androidx.compose.ui.unit.dp
16 | import com.rocketseat.nlw.nearby.ui.component.button.NearbyButton
17 | import com.rocketseat.nlw.nearby.ui.component.welcome.WelcomeContent
18 | import com.rocketseat.nlw.nearby.ui.component.welcome.WelcomeHeader
19 |
20 | @Composable
21 | fun WelcomeScreen(modifier: Modifier = Modifier, onNavigateToHome: () -> Unit) {
22 | Column(
23 | modifier = modifier
24 | .background(Color.White)
25 | .fillMaxSize()
26 | .padding(horizontal = 40.dp, vertical = 48.dp)
27 | .verticalScroll(state = rememberScrollState()),
28 | verticalArrangement = Arrangement.SpaceBetween
29 | ) {
30 | WelcomeHeader()
31 | WelcomeContent()
32 | NearbyButton(
33 | modifier = Modifier.fillMaxWidth(),
34 | text = "Começar",
35 | onClick = onNavigateToHome
36 | )
37 | }
38 | }
39 |
40 | @Preview
41 | @Composable
42 | private fun WelcomeScreenPreview() {
43 | WelcomeScreen(onNavigateToHome = {})
44 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_shopping_cart.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_map_pin.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_ticket.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_shopping_bag.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_loader.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/core/network/NearbyRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.core.network
2 |
3 | import com.rocketseat.nlw.nearby.core.network.KtorHttpClient.httpClientAndroid
4 | import com.rocketseat.nlw.nearby.data.model.Category
5 | import com.rocketseat.nlw.nearby.data.model.Coupon
6 | import com.rocketseat.nlw.nearby.data.model.Market
7 | import com.rocketseat.nlw.nearby.data.model.MarketDetails
8 | import io.ktor.client.call.body
9 | import io.ktor.client.request.get
10 | import io.ktor.client.request.patch
11 |
12 | object NearbyRemoteDataSource {
13 |
14 | private const val LOCAL_HOST_EMULATOR_BASE_URL = "http://10.0.2.2:3333"
15 | private const val LOCAL_HOST_PHYSICAL_BASE_URL = "http://SEU_SUB_DOMINIO_AQUI:3333"
16 |
17 | private const val BASE_URL = LOCAL_HOST_EMULATOR_BASE_URL
18 |
19 | // 1 - Busca de categorias
20 | // 2 - Busca de Locais (com base em uma categoria)
21 | // 3 - Busca de detalhes de um local (com base em um local especifico)
22 | // 4 - Gerar cupom a partir de leitura do qrcode
23 |
24 | suspend fun getCategories(): Result> = try {
25 | val categories = httpClientAndroid.get("$BASE_URL/categories")
26 | .body>()
27 |
28 | Result.success(categories)
29 | } catch (e: Exception) {
30 | Result.failure(e)
31 | }
32 |
33 | suspend fun getMarkets(categoryId: String): Result> = try {
34 | val markets = httpClientAndroid.get("$BASE_URL/markets/category/${categoryId}")
35 | .body>()
36 |
37 | Result.success(markets)
38 | } catch (e: Exception) {
39 | Result.failure(e)
40 | }
41 |
42 | suspend fun getMarketDetails(marketId: String): Result = try {
43 | val market = httpClientAndroid.get("$BASE_URL/markets/${marketId}")
44 | .body()
45 |
46 | Result.success(market)
47 | } catch (e: Exception) {
48 | Result.failure(e)
49 | }
50 |
51 | suspend fun patchCoupon(marketId: String): Result = try {
52 | val coupon = httpClientAndroid.patch("$BASE_URL/coupons/${marketId}")
53 | .body()
54 |
55 | Result.success(coupon)
56 | } catch (e: Exception) {
57 | Result.failure(e)
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/category/NearbyCategoryFilterChipList.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.category
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.lazy.LazyRow
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.LaunchedEffect
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import com.rocketseat.nlw.nearby.data.model.Category
18 | import com.rocketseat.nlw.nearby.data.model.mock.mockCategories
19 |
20 | @Composable
21 | fun NearbyCategoryFilterChipList(
22 | modifier: Modifier = Modifier,
23 | categories: List,
24 | onSelectedCategoryChanged: (Category) -> Unit
25 | ) {
26 | var selectedCategoryId by remember { mutableStateOf(categories.firstOrNull()?.id.orEmpty()) }
27 |
28 | LaunchedEffect(key1 = selectedCategoryId) {
29 | val selectedCategoryOrNull = categories.find { it.id == selectedCategoryId }
30 | selectedCategoryOrNull?.let {
31 | onSelectedCategoryChanged(it)
32 | }
33 | }
34 |
35 | LazyRow(
36 | modifier = modifier,
37 | contentPadding = PaddingValues(horizontal = 24.dp),
38 | horizontalArrangement = Arrangement.spacedBy(8.dp)
39 | ) {
40 | items(items = categories, key = { it.id }) { category ->
41 | NearbyCategoryFilterChip(
42 | category = category,
43 | isSelected = category.id == selectedCategoryId,
44 | onClick = { isSelected ->
45 | if (isSelected)
46 | selectedCategoryId = category.id
47 | }
48 | )
49 | }
50 | }
51 | }
52 |
53 | @Preview
54 | @Composable
55 | private fun NearbyCategoryFilterChipListPreview() {
56 | NearbyCategoryFilterChipList(
57 | modifier = Modifier.fillMaxWidth(),
58 | categories = mockCategories,
59 | onSelectedCategoryChanged = {}
60 | )
61 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_qrcode.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.home
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.google.android.gms.maps.model.LatLng
6 | import com.rocketseat.nlw.nearby.core.network.NearbyRemoteDataSource
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import kotlinx.coroutines.flow.StateFlow
9 | import kotlinx.coroutines.flow.asStateFlow
10 | import kotlinx.coroutines.flow.update
11 | import kotlinx.coroutines.launch
12 |
13 | class HomeViewModel : ViewModel() {
14 | private val _uiState = MutableStateFlow(HomeUiState())
15 | val uiState: StateFlow = _uiState.asStateFlow()
16 |
17 | fun onEvent(event: HomeUiEvent) {
18 | when (event) {
19 | is HomeUiEvent.OnFetchCategories -> fetchCategories()
20 | is HomeUiEvent.OnFetchMarkets -> fetchMarkets(categoryId = event.categoryId)
21 | }
22 | }
23 |
24 | private fun fetchCategories() {
25 | viewModelScope.launch {
26 | _uiState.update { currentUiState ->
27 | NearbyRemoteDataSource.getCategories().fold(
28 | onSuccess = { categories ->
29 | currentUiState.copy(
30 | categories = categories
31 | )
32 | },
33 | onFailure = { error ->
34 | currentUiState.copy(
35 | categories = emptyList()
36 | )
37 | }
38 | )
39 | }
40 | }
41 | }
42 |
43 | private fun fetchMarkets(categoryId: String) {
44 | viewModelScope.launch {
45 | _uiState.update { currentUiState ->
46 | NearbyRemoteDataSource.getMarkets(categoryId = categoryId).fold(
47 | onSuccess = { markets ->
48 | currentUiState.copy(
49 | markets = markets,
50 | marketLocations = markets.map { market ->
51 | LatLng(market.latitude, market.longitude)
52 | }
53 | )
54 | },
55 | onFailure = { error ->
56 | currentUiState.copy(
57 | markets = emptyList(),
58 | marketLocations = emptyList()
59 | )
60 | }
61 | )
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.compose.compiler)
5 | kotlin("plugin.serialization") version "2.0.21"
6 | }
7 |
8 | android {
9 | namespace = "com.rocketseat.nlw.nearby"
10 | compileSdk = 35
11 |
12 | defaultConfig {
13 | applicationId = "com.rocketseat.nlw.nearby"
14 | minSdk = 24
15 | targetSdk = 35
16 | versionCode = 1
17 | versionName = "1.0"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary = true
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = false
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | }
33 | }
34 | compileOptions {
35 | sourceCompatibility = JavaVersion.VERSION_1_8
36 | targetCompatibility = JavaVersion.VERSION_1_8
37 | }
38 | kotlinOptions {
39 | jvmTarget = "1.8"
40 | }
41 | buildFeatures {
42 | compose = true
43 | }
44 | composeOptions {
45 | kotlinCompilerExtensionVersion = "1.5.1"
46 | }
47 | packaging {
48 | resources {
49 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
50 | }
51 | }
52 | }
53 |
54 | dependencies {
55 | implementation(libs.maps.compose)
56 | implementation(libs.coil.compose)
57 | implementation(libs.coil.network.okhttp)
58 | implementation(libs.navigation.compose)
59 | implementation(libs.kotlin.serialization)
60 |
61 | implementation(platform(libs.ktor.bom))
62 | implementation(libs.bundles.ktor.client)
63 |
64 | implementation(libs.zxing)
65 | implementation(libs.zxing.android.embedded)
66 |
67 | implementation(libs.androidx.core.ktx)
68 | implementation(libs.androidx.lifecycle.runtime.ktx)
69 | implementation(libs.androidx.activity.compose)
70 | implementation(platform(libs.androidx.compose.bom))
71 | implementation(libs.androidx.ui)
72 | implementation(libs.androidx.ui.graphics)
73 | implementation(libs.androidx.ui.tooling.preview)
74 | implementation(libs.androidx.material3)
75 | testImplementation(libs.junit)
76 | androidTestImplementation(libs.androidx.junit)
77 | androidTestImplementation(libs.androidx.espresso.core)
78 | androidTestImplementation(platform(libs.androidx.compose.bom))
79 | androidTestImplementation(libs.androidx.ui.test.junit4)
80 | debugImplementation(libs.androidx.ui.tooling)
81 | debugImplementation(libs.androidx.ui.test.manifest)
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/market_details/NearbyMarketDetailsCoupons.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.market_details
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.size
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.clip
18 | import androidx.compose.ui.res.painterResource
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 | import com.rocketseat.nlw.nearby.R
22 | import com.rocketseat.nlw.nearby.ui.theme.Gray400
23 | import com.rocketseat.nlw.nearby.ui.theme.GreenBase
24 | import com.rocketseat.nlw.nearby.ui.theme.GreenExtraLight
25 | import com.rocketseat.nlw.nearby.ui.theme.Typography
26 |
27 | @Composable
28 | fun NearbyMarketDetailsCoupons(modifier: Modifier = Modifier, coupons: List) {
29 | Column(
30 | modifier = modifier,
31 | verticalArrangement = Arrangement.spacedBy(16.dp)
32 | ) {
33 | Text(text = "Utilize esse cupom", style = Typography.headlineSmall, color = Gray400)
34 | coupons.forEach { coupon ->
35 | Row(
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .height(48.dp)
39 | .clip(RoundedCornerShape(8.dp))
40 | .background(GreenExtraLight)
41 | .padding(8.dp),
42 | verticalAlignment = Alignment.CenterVertically,
43 | horizontalArrangement = Arrangement.spacedBy(8.dp)
44 | ) {
45 | Icon(
46 | modifier = Modifier.size(24.dp),
47 | painter = painterResource(R.drawable.ic_ticket),
48 | tint = GreenBase,
49 | contentDescription = "Ícone Cupons"
50 | )
51 | Text(text = coupon, style = Typography.headlineSmall)
52 | }
53 | }
54 | }
55 | }
56 |
57 | @Preview
58 | @Composable
59 | private fun MarketDetailsCouponsPreview() {
60 | NearbyMarketDetailsCoupons(
61 | modifier = Modifier.fillMaxWidth(),
62 | coupons = listOf("FM4345T5", "FM4345T6")
63 | )
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/button/NearbyButton.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.button
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.heightIn
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material3.Button
11 | import androidx.compose.material3.ButtonDefaults
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.painterResource
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import com.rocketseat.nlw.nearby.R
21 | import com.rocketseat.nlw.nearby.ui.theme.GreenBase
22 | import com.rocketseat.nlw.nearby.ui.theme.Typography
23 |
24 | @Composable
25 | fun NearbyButton(
26 | modifier: Modifier = Modifier,
27 | text: String? = null,
28 | @DrawableRes iconRes: Int? = null,
29 | onClick: () -> Unit
30 | ) {
31 | Button(
32 | modifier = modifier.heightIn(min = 56.dp),
33 | shape = RoundedCornerShape(16.dp),
34 | contentPadding = if (text == null && iconRes != null) PaddingValues(0.dp) else ButtonDefaults.ContentPadding,
35 | colors = ButtonDefaults.buttonColors(
36 | containerColor = GreenBase
37 | ),
38 | onClick = onClick
39 | ) {
40 | Row(
41 | verticalAlignment = Alignment.CenterVertically,
42 | horizontalArrangement = Arrangement.spacedBy(8.dp)
43 | ) {
44 | iconRes?.let {
45 | Icon(painter = painterResource(id = iconRes), contentDescription = "Ícone do Botão")
46 | }
47 | text?.let { Text(text = text.uppercase(), style = Typography.labelLarge) }
48 | }
49 | }
50 | }
51 |
52 | @Preview
53 | @Composable
54 | private fun NearbyButtonPreview() {
55 | NearbyButton(
56 | modifier = Modifier.fillMaxWidth(),
57 | text = "Confirmar",
58 | iconRes = R.drawable.ic_scan
59 | ) {}
60 | }
61 |
62 | @Preview
63 | @Composable
64 | private fun NearbyButtonNoIconPreview() {
65 | NearbyButton(
66 | modifier = Modifier.fillMaxWidth(),
67 | text = "Confirmar",
68 | ) {}
69 | }
70 |
71 | @Preview
72 | @Composable
73 | private fun NearbyButtonNoTextPreview() {
74 | NearbyButton(
75 | modifier = Modifier,
76 | iconRes = R.drawable.ic_arrow_left
77 | ) {}
78 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import com.rocketseat.nlw.nearby.R
10 |
11 | val rubikFontFamily = FontFamily(
12 | Font(R.font.rubik, FontWeight.Normal),
13 | Font(R.font.rubik_medium, FontWeight.Medium),
14 | Font(R.font.rubik_bold, FontWeight.Bold),
15 | Font(R.font.rubik_semibold, FontWeight.SemiBold)
16 | )
17 |
18 | private const val activatePreview = true
19 |
20 | val Typography = Typography(
21 | // "Title XI"
22 | headlineLarge = TextStyle(
23 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
24 | fontWeight = FontWeight.Bold,
25 | fontSize = 24.sp
26 | ),
27 | // "Title Lg"
28 | headlineMedium = TextStyle(
29 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
30 | fontWeight = FontWeight.Bold,
31 | fontSize = 20.sp
32 | ),
33 | // "Title Md"
34 | headlineSmall = TextStyle(
35 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
36 | fontWeight = FontWeight.SemiBold,
37 | fontSize = 16.sp
38 | ),
39 | // "Text Sm"
40 | titleLarge = TextStyle(
41 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
42 | fontWeight = FontWeight.SemiBold,
43 | fontSize = 14.sp
44 | ),
45 | // "Text Md"
46 | bodyLarge = TextStyle(
47 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
48 | fontWeight = FontWeight.Normal,
49 | fontSize = 16.sp
50 | ),
51 | // "Text Sm"
52 | bodyMedium = TextStyle(
53 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
54 | fontWeight = FontWeight.Normal,
55 | fontSize = 14.sp
56 | ),
57 | // "Text Xs"
58 | bodySmall = TextStyle(
59 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
60 | fontWeight = FontWeight.Normal,
61 | fontSize = 12.sp
62 | ),
63 | // "Action"
64 | labelLarge = TextStyle(
65 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
66 | fontWeight = FontWeight.SemiBold,
67 | fontSize = 16.sp
68 | ),
69 | // "Subtitle"
70 | labelMedium = TextStyle(
71 | fontFamily = if (activatePreview) FontFamily.Default else rubikFontFamily,
72 | fontWeight = FontWeight.Medium,
73 | fontSize = 14.sp
74 | )
75 | )
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/market_details/MarketDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.market_details
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.rocketseat.nlw.nearby.core.network.NearbyRemoteDataSource
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 | import kotlinx.coroutines.flow.StateFlow
8 | import kotlinx.coroutines.flow.asStateFlow
9 | import kotlinx.coroutines.flow.update
10 | import kotlinx.coroutines.launch
11 |
12 | class MarketDetailsViewModel : ViewModel() {
13 |
14 | private val _uiState = MutableStateFlow(MarketDetailsUiState())
15 | val uiState: StateFlow = _uiState.asStateFlow()
16 |
17 | fun onEvent(event: MarketDetailsUiEvent) {
18 | when (event) {
19 | is MarketDetailsUiEvent.OnFetchCoupon -> fetchCoupon(qrCodeContent = event.qrCodeContent)
20 | is MarketDetailsUiEvent.OnFetchRules -> fetchRules(marketId = event.marketId)
21 | MarketDetailsUiEvent.OnResetCoupon -> resetCoupon()
22 | }
23 | }
24 |
25 | private fun fetchCoupon(qrCodeContent: String) {
26 | viewModelScope.launch {
27 | NearbyRemoteDataSource.patchCoupon(marketId = qrCodeContent).fold(
28 | onSuccess = { coupon ->
29 | _uiState.update { currentUiState ->
30 | currentUiState.copy(
31 | coupon = coupon.coupon
32 | )
33 | }
34 | },
35 | onFailure = {
36 | _uiState.update { currentUiState ->
37 | currentUiState.copy(
38 | coupon = ""
39 | )
40 | }
41 | }
42 | )
43 | }
44 | }
45 |
46 | private fun fetchRules(marketId: String) {
47 | viewModelScope.launch {
48 | NearbyRemoteDataSource.getMarketDetails(marketId = marketId).fold(
49 | onSuccess = { marketDetails ->
50 | _uiState.update { currentUiState ->
51 | currentUiState.copy(
52 | rules = marketDetails.rules
53 | )
54 | }
55 | },
56 | onFailure = {
57 | _uiState.update { currentUiState ->
58 | currentUiState.copy(
59 | rules = emptyList()
60 | )
61 | }
62 | }
63 | )
64 | }
65 | }
66 |
67 | private fun resetCoupon() {
68 | _uiState.update { currentUiState ->
69 | currentUiState.copy(
70 | coupon = null
71 | )
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/qrcode_scanner/QRCodeScannerScreen.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.qrcode_scanner
2 |
3 | import android.widget.Toast
4 | import androidx.activity.compose.rememberLauncherForActivityResult
5 | import androidx.activity.result.contract.ActivityResultContract
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.LaunchedEffect
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
14 | import androidx.core.content.ContextCompat
15 | import com.journeyapps.barcodescanner.ScanContract
16 | import com.journeyapps.barcodescanner.ScanOptions
17 | import com.rocketseat.nlw.nearby.MainActivity
18 |
19 | @Composable
20 | fun QRCodeScannerScreen(modifier: Modifier = Modifier, onCompletedScan: (String) -> Unit) {
21 | val context = LocalContext.current
22 |
23 | val scanOptions = ScanOptions()
24 | .setDesiredBarcodeFormats(ScanOptions.QR_CODE)
25 | .setPrompt("Leia o QRCode do cupom")
26 | .setCameraId(0)
27 | .setBeepEnabled(false)
28 | .setOrientationLocked(false)
29 | .setBarcodeImageEnabled(true)
30 |
31 | val barcodeLauncher = rememberLauncherForActivityResult(
32 | ScanContract()
33 | ) { result ->
34 | onCompletedScan(result.contents.orEmpty())
35 | }
36 |
37 | val cameraPermissionLauncher = rememberLauncherForActivityResult(
38 | ActivityResultContracts.RequestPermission()
39 | ) { isGranted ->
40 | if (!isGranted)
41 | ActivityResultContracts.RequestPermission()
42 | else
43 | barcodeLauncher.launch(scanOptions)
44 | }
45 |
46 | fun checkCameraPermission() {
47 | if (ContextCompat.checkSelfPermission(
48 | context,
49 | android.Manifest.permission.CAMERA
50 | ) == android.content.pm.PackageManager.PERMISSION_GRANTED
51 | ) {
52 | barcodeLauncher.launch(scanOptions)
53 | } else if (shouldShowRequestPermissionRationale(
54 | context as MainActivity,
55 | android.Manifest.permission.CAMERA
56 | )
57 | ) {
58 | Toast.makeText(
59 | context,
60 | "Necessário permitir o acesso à câmera para continuar.",
61 | Toast.LENGTH_SHORT
62 | ).show()
63 | } else {
64 | cameraPermissionLauncher.launch(android.Manifest.permission.CAMERA)
65 | }
66 | }
67 |
68 | LaunchedEffect(true) {
69 | checkCameraPermission()
70 | }
71 |
72 | Column(modifier = modifier.fillMaxSize()) { }
73 |
74 |
75 | }
--------------------------------------------------------------------------------
/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 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/category/NearbyCategoryFilterChip.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.category
2 |
3 | import androidx.compose.foundation.layout.heightIn
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.material3.FilterChip
7 | import androidx.compose.material3.FilterChipDefaults
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.res.painterResource
14 | import androidx.compose.ui.tooling.preview.Preview
15 | import androidx.compose.ui.unit.dp
16 | import com.rocketseat.nlw.nearby.data.model.Category
17 | import com.rocketseat.nlw.nearby.ui.theme.Gray300
18 | import com.rocketseat.nlw.nearby.ui.theme.Gray400
19 | import com.rocketseat.nlw.nearby.ui.theme.GreenBase
20 | import com.rocketseat.nlw.nearby.ui.theme.Typography
21 |
22 | @Composable
23 | fun NearbyCategoryFilterChip(
24 | modifier: Modifier = Modifier,
25 | category: Category,
26 | isSelected: Boolean,
27 | onClick: (isSelected: Boolean) -> Unit
28 | ) {
29 | FilterChip(
30 | modifier = modifier
31 | .padding(2.dp)
32 | .heightIn(min = 36.dp),
33 | elevation = FilterChipDefaults.filterChipElevation(
34 | elevation = 8.dp
35 | ),
36 | leadingIcon = {
37 | category.icon?.let {
38 | Icon(
39 | modifier = Modifier.size(16.dp),
40 | painter = painterResource(id = it),
41 | tint = if (isSelected) Color.White else Gray400,
42 | contentDescription = "Ícone de Filtro de Categoria"
43 | )
44 | }
45 | },
46 | border = FilterChipDefaults.filterChipBorder(
47 | enabled = false,
48 | selected = isSelected,
49 | disabledBorderColor = Gray300,
50 | borderWidth = 1.dp,
51 | selectedBorderWidth = 0.dp,
52 | selectedBorderColor = Color.Transparent
53 | ),
54 | colors = FilterChipDefaults.filterChipColors(
55 | containerColor = Color.White,
56 | selectedContainerColor = GreenBase,
57 | ),
58 | selected = isSelected,
59 | onClick = { onClick(!isSelected) },
60 | label = {
61 | Text(
62 | text = category.name,
63 | style = Typography.bodyMedium,
64 | color = if (isSelected) Color.White else Gray400
65 | )
66 | }
67 | )
68 | }
69 |
70 | @Preview
71 | @Composable
72 | private fun NearbyCategoryFilterChipPreview() {
73 | NearbyCategoryFilterChip(
74 | category = Category(
75 | id = "1",
76 | name = "Alimentação"
77 | ),
78 | isSelected = true,
79 | onClick = {}
80 | )
81 | }
82 |
83 |
84 | @Preview
85 | @Composable
86 | private fun NearbyCategoryFilterChipNotSelectedPreview() {
87 | NearbyCategoryFilterChip(
88 | category = Category(
89 | id = "1",
90 | name = "Cinema"
91 | ),
92 | isSelected = false,
93 | onClick = {}
94 | )
95 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/market_details/NearbyMarketDetailsInfos.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.market_details
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.res.painterResource
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.dp
15 | import com.rocketseat.nlw.nearby.R
16 | import com.rocketseat.nlw.nearby.data.model.Market
17 | import com.rocketseat.nlw.nearby.data.model.mock.mockMarkets
18 | import com.rocketseat.nlw.nearby.ui.theme.Gray400
19 | import com.rocketseat.nlw.nearby.ui.theme.Gray500
20 | import com.rocketseat.nlw.nearby.ui.theme.Typography
21 |
22 | @Composable
23 | fun NearbyMarketDetailsInfos(modifier: Modifier = Modifier, market: Market) {
24 | Column(
25 | modifier = modifier,
26 | verticalArrangement = Arrangement.spacedBy(16.dp)
27 | ) {
28 | Text(text = "Informações", style = Typography.headlineSmall, color = Gray400)
29 |
30 | Column(
31 | verticalArrangement = Arrangement.spacedBy(8.dp)
32 | ) {
33 | Row(
34 | horizontalArrangement = Arrangement.spacedBy(8.dp)
35 | ) {
36 | Icon(
37 | modifier = Modifier.size(16.dp),
38 | painter = painterResource(id = R.drawable.ic_ticket),
39 | tint = Gray500,
40 | contentDescription = "Ícone Cupons"
41 | )
42 | Text(
43 | text = "${market.coupons} cupons disponíveis",
44 | style = Typography.labelMedium,
45 | color = Gray500
46 | )
47 | }
48 |
49 | Row(
50 | horizontalArrangement = Arrangement.spacedBy(8.dp)
51 | ) {
52 | Icon(
53 | modifier = Modifier.size(16.dp),
54 | painter = painterResource(id = R.drawable.ic_map_pin),
55 | tint = Gray500,
56 | contentDescription = "Ícone Endereço"
57 | )
58 | Text(
59 | text = market.address,
60 | style = Typography.labelMedium,
61 | color = Gray500
62 | )
63 | }
64 |
65 | Row(
66 | horizontalArrangement = Arrangement.spacedBy(8.dp)
67 | ) {
68 | Icon(
69 | modifier = Modifier.size(16.dp),
70 | painter = painterResource(id = R.drawable.ic_phone),
71 | tint = Gray500,
72 | contentDescription = "Ícone Telefone"
73 | )
74 | Text(
75 | text = market.phone,
76 | style = Typography.labelMedium,
77 | color = Gray500
78 | )
79 | }
80 | }
81 | }
82 | }
83 |
84 | @Preview
85 | @Composable
86 | private fun MarketDetailsInfosPreview() {
87 | NearbyMarketDetailsInfos(
88 | modifier = Modifier.fillMaxWidth(),
89 | market = mockMarkets.first()
90 | )
91 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/font_certs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @array/com_google_android_gms_fonts_certs_dev
5 | - @array/com_google_android_gms_fonts_certs_prod
6 |
7 |
8 | -
9 | MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
10 |
11 |
12 |
13 | -
14 | MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.6.1"
3 | kotlin = "2.0.20"
4 | coreKtx = "1.15.0"
5 | junit = "4.13.2"
6 | junitVersion = "1.2.1"
7 | espressoCore = "3.6.1"
8 | lifecycleRuntimeKtx = "2.8.7"
9 | activityCompose = "1.9.3"
10 | composeBom = "2024.10.00"
11 | mapsCompose = "4.3.3"
12 | coil = "3.0.0-rc01"
13 | navigation = "2.8.3"
14 | kotlinSerialization = "1.7.3"
15 | ktorBom = "3.0.0"
16 | zxing = "3.5.1"
17 | zxingAndroidEmbedded = "4.3.0"
18 |
19 | [libraries]
20 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
21 | junit = { group = "junit", name = "junit", version.ref = "junit" }
22 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
23 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
24 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
25 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
26 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
27 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
28 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
29 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
30 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
31 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
32 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
33 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
34 | maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "mapsCompose" }
35 | coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
36 | coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
37 | navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
38 | kotlin-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinSerialization" }
39 | ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktorBom" }
40 | ktor-client-android = { group = "io.ktor", name = "ktor-client-android" }
41 | ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation" }
42 | ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging" }
43 | ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization" }
44 | ktor-client-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" }
45 | zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
46 | zxing-android-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
47 |
48 | [plugins]
49 | android-application = { id = "com.android.application", version.ref = "agp" }
50 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
51 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
52 |
53 | [bundles]
54 | ktor-client = [
55 | "ktor-client-android",
56 | "ktor-client-content-negotiation",
57 | "ktor-client-logging",
58 | "ktor-client-serialization",
59 | "ktor-client-serialization-json"
60 | ]
61 |
62 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/home/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.home
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.shape.RoundedCornerShape
8 | import androidx.compose.material3.BottomSheetScaffold
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.rememberBottomSheetScaffoldState
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.LaunchedEffect
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.platform.LocalConfiguration
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import com.rocketseat.nlw.nearby.data.model.Market
19 | import com.rocketseat.nlw.nearby.ui.component.category.NearbyCategoryFilterChipList
20 | import com.rocketseat.nlw.nearby.ui.component.home.NearbyGoogleMap
21 | import com.rocketseat.nlw.nearby.ui.component.market.NearbyMarketCardList
22 | import com.rocketseat.nlw.nearby.ui.theme.Gray100
23 |
24 | @OptIn(ExperimentalMaterial3Api::class)
25 | @Composable
26 | fun HomeScreen(
27 | modifier: Modifier = Modifier,
28 | uiState: HomeUiState,
29 | onEvent: (HomeUiEvent) -> Unit,
30 | onNavigateToMarketDetails: (Market) -> Unit
31 | ) {
32 | val bottomSheetState = rememberBottomSheetScaffoldState()
33 |
34 | val configuration = LocalConfiguration.current
35 |
36 | LaunchedEffect(true) {
37 | onEvent(HomeUiEvent.OnFetchCategories)
38 | }
39 |
40 |
41 | BottomSheetScaffold(
42 | modifier = modifier,
43 | scaffoldState = bottomSheetState,
44 | sheetContainerColor = Gray100,
45 | sheetPeekHeight = configuration.screenHeightDp.dp * 0.5f,
46 | sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
47 | sheetContent = {
48 | if (!uiState.markets.isNullOrEmpty())
49 | NearbyMarketCardList(
50 | modifier = Modifier
51 | .fillMaxSize()
52 | .padding(16.dp),
53 | markets = uiState.markets,
54 | onMarketClick = { selectedMarket ->
55 | onNavigateToMarketDetails(selectedMarket)
56 | }
57 | )
58 | },
59 | content = {
60 | Box(
61 | modifier = Modifier
62 | .fillMaxSize()
63 | .padding(
64 | bottom = it
65 | .calculateBottomPadding()
66 | .minus(8.dp)
67 | )
68 | ) {
69 | NearbyGoogleMap(uiState = uiState)
70 |
71 | if (!uiState.categories.isNullOrEmpty())
72 | NearbyCategoryFilterChipList(
73 | modifier = Modifier
74 | .fillMaxWidth()
75 | .padding(top = 24.dp)
76 | .align(Alignment.TopStart),
77 | categories = uiState.categories,
78 | onSelectedCategoryChanged = { selectedCategory ->
79 | onEvent(HomeUiEvent.OnFetchMarkets(categoryId = selectedCategory.id))
80 | }
81 | )
82 | }
83 | }
84 | )
85 | }
86 |
87 | @Preview
88 | @Composable
89 | private fun HomeScreenPreview() {
90 | HomeScreen(
91 | onNavigateToMarketDetails = {},
92 | uiState = HomeUiState(),
93 | onEvent = {}
94 | )
95 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_nearby_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import androidx.activity.viewModels
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
12 | import androidx.navigation.compose.NavHost
13 | import androidx.navigation.compose.composable
14 | import androidx.navigation.compose.rememberNavController
15 | import androidx.navigation.toRoute
16 | import com.rocketseat.nlw.nearby.data.model.Market
17 | import com.rocketseat.nlw.nearby.ui.screen.home.HomeScreen
18 | import com.rocketseat.nlw.nearby.ui.screen.home.HomeViewModel
19 | import com.rocketseat.nlw.nearby.ui.screen.market_details.MarketDetailsScreen
20 | import com.rocketseat.nlw.nearby.ui.screen.splash.SplashScreen
21 | import com.rocketseat.nlw.nearby.ui.screen.welcome.WelcomeScreen
22 | import com.rocketseat.nlw.nearby.ui.route.Home
23 | import com.rocketseat.nlw.nearby.ui.route.QRCodeScanner
24 | import com.rocketseat.nlw.nearby.ui.route.Splash
25 | import com.rocketseat.nlw.nearby.ui.route.Welcome
26 | import com.rocketseat.nlw.nearby.ui.screen.market_details.MarketDetailsUiEvent
27 | import com.rocketseat.nlw.nearby.ui.screen.market_details.MarketDetailsViewModel
28 | import com.rocketseat.nlw.nearby.ui.screen.qrcode_scanner.QRCodeScannerScreen
29 | import com.rocketseat.nlw.nearby.ui.theme.NearbyTheme
30 |
31 | class MainActivity : ComponentActivity() {
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | enableEdgeToEdge()
35 | setContent {
36 | NearbyTheme {
37 | val navController = rememberNavController()
38 |
39 | val homeViewModel by viewModels()
40 | val homeUiState by homeViewModel.uiState.collectAsStateWithLifecycle()
41 |
42 | val marketDetailsViewModel by viewModels()
43 | val marketDetailsUiState by marketDetailsViewModel.uiState.collectAsStateWithLifecycle()
44 |
45 | NavHost(
46 | navController = navController,
47 | startDestination = Splash
48 | ) {
49 | composable {
50 | SplashScreen(
51 | onNavigateToWelcome = {
52 | navController.navigate(Welcome)
53 | }
54 | )
55 | }
56 | composable {
57 | WelcomeScreen(
58 | onNavigateToHome = {
59 | navController.navigate(Home)
60 | }
61 | )
62 | }
63 | composable {
64 | HomeScreen(
65 | onNavigateToMarketDetails = { selectedMarket ->
66 | navController.navigate(selectedMarket)
67 | },
68 | uiState = homeUiState,
69 | onEvent = homeViewModel::onEvent
70 | )
71 | }
72 | composable {
73 | val selectedMarket = it.toRoute()
74 |
75 | MarketDetailsScreen(
76 | market = selectedMarket,
77 | uiState = marketDetailsUiState,
78 | onEvent = marketDetailsViewModel::onEvent,
79 | onNavigateBack = {
80 | navController.popBackStack()
81 | },
82 | onNavigateToQRCodeScanner = {
83 | navController.navigate(QRCodeScanner)
84 | }
85 | )
86 | }
87 |
88 | composable {
89 | QRCodeScannerScreen(
90 | onCompletedScan = { qrCodeContent ->
91 | if (qrCodeContent.isNotEmpty()) {
92 | marketDetailsViewModel.onEvent(
93 | MarketDetailsUiEvent.OnFetchCoupon(
94 | qrCodeContent
95 | )
96 | )
97 | navController.popBackStack()
98 | }
99 | }
100 | )
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | @Preview(showBackground = true)
109 | @Composable
110 | fun GreetingPreview() {
111 | NearbyTheme {
112 |
113 | }
114 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/market/NearbyMarketCard.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.market
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.IntrinsicSize
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.fillMaxWidth
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.foundation.layout.size
16 | import androidx.compose.foundation.shape.RoundedCornerShape
17 | import androidx.compose.material3.Card
18 | import androidx.compose.material3.Icon
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.draw.clip
24 | import androidx.compose.ui.layout.ContentScale
25 | import androidx.compose.ui.res.painterResource
26 | import androidx.compose.ui.text.style.TextOverflow
27 | import androidx.compose.ui.tooling.preview.Preview
28 | import androidx.compose.ui.unit.dp
29 | import androidx.compose.ui.unit.sp
30 | import coil3.compose.AsyncImage
31 | import com.rocketseat.nlw.nearby.R
32 | import com.rocketseat.nlw.nearby.data.model.Market
33 | import com.rocketseat.nlw.nearby.ui.theme.Gray100
34 | import com.rocketseat.nlw.nearby.ui.theme.Gray200
35 | import com.rocketseat.nlw.nearby.ui.theme.Gray400
36 | import com.rocketseat.nlw.nearby.ui.theme.Gray500
37 | import com.rocketseat.nlw.nearby.ui.theme.RedBase
38 | import com.rocketseat.nlw.nearby.ui.theme.Typography
39 |
40 | @Composable
41 | fun NearbyMarketCard(
42 | modifier: Modifier = Modifier,
43 | market: Market,
44 | onClick: (Market) -> Unit
45 | ) {
46 | Card(
47 | modifier = modifier
48 | .clip(RoundedCornerShape(12.dp))
49 | .background(Gray100)
50 | .border(width = 1.dp, color = Gray200, shape = RoundedCornerShape(12.dp)),
51 | onClick = {
52 | onClick(market)
53 | }
54 | ) {
55 | Row(
56 | modifier = Modifier
57 | .fillMaxWidth()
58 | .background(Gray100)
59 | .padding(8.dp),
60 | verticalAlignment = Alignment.CenterVertically,
61 | horizontalArrangement = Arrangement.spacedBy(16.dp)
62 | ) {
63 | AsyncImage(
64 | model = market.cover,
65 | modifier = Modifier
66 | .clip(RoundedCornerShape(12.dp))
67 | .fillMaxWidth(0.3f)
68 | .height(IntrinsicSize.Min)
69 | .aspectRatio(ratio = 1f, matchHeightConstraintsFirst = true),
70 | contentScale = ContentScale.Crop,
71 | contentDescription = "Imagem do Estabelecimento"
72 | )
73 | Column {
74 | Text(text = market.name, style = Typography.headlineSmall.copy(fontSize = 14.sp))
75 | Spacer(modifier = Modifier.height(8.dp))
76 | Text(
77 | text = market.description,
78 | maxLines = 2,
79 | overflow = TextOverflow.Ellipsis,
80 | color = Gray500,
81 | style = Typography.bodyLarge.copy(fontSize = 12.sp)
82 | )
83 | Spacer(modifier = Modifier.height(16.dp))
84 | Row(
85 | verticalAlignment = Alignment.CenterVertically,
86 | horizontalArrangement = Arrangement.spacedBy(8.dp)
87 | ) {
88 | Icon(
89 | modifier = Modifier.size(24.dp),
90 | tint = if (market.coupons > 0) RedBase else Gray400,
91 | painter = painterResource(id = R.drawable.ic_ticket),
92 | contentDescription = "Ícone de Cupom"
93 | )
94 | Text(
95 | text = "${market.coupons} cupons disponíveis",
96 | color = Gray400,
97 | style = Typography.bodyMedium.copy(fontSize = 12.sp)
98 | )
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | @Preview
106 | @Composable
107 | private fun NearbyMarketCardPreview() {
108 | NearbyMarketCard(
109 | modifier = Modifier.fillMaxWidth(),
110 | market = Market(
111 | id = "012576ea-4441-4b8a-89e5-d5f32104c7c4",
112 | categoryId = "146b1a88-b3d3-4232-8b8f-c1f006f1e86d",
113 | name = "Sabor Grill",
114 | description = "Churrascaria com cortes nobres e buffet variado. Experiência completa para os amantes de carne.",
115 | coupons = 10,
116 | // rules = emptyList(),
117 | latitude = -23.55974230991911,
118 | longitude = -46.65814845249887,
119 | address = "Av. Paulista - Bela Vista",
120 | phone = "(11) 94567-1212",
121 | cover = "https://images.unsplash.com/photo-1498654896293-37aacf113fd9?w=400&h=300"
122 | ),
123 | onClick = {}
124 | )
125 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/screen/market_details/MarketDetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.screen.market_details
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxHeight
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.rememberScrollState
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.foundation.verticalScroll
15 | import androidx.compose.material3.HorizontalDivider
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.LaunchedEffect
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.layout.ContentScale
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.dp
26 | import coil3.compose.AsyncImage
27 | import com.rocketseat.nlw.nearby.R
28 | import com.rocketseat.nlw.nearby.data.model.Market
29 | import com.rocketseat.nlw.nearby.data.model.mock.mockMarkets
30 | import com.rocketseat.nlw.nearby.ui.component.button.NearbyButton
31 | import com.rocketseat.nlw.nearby.ui.component.market_details.NearbyMarketDetailsCoupons
32 | import com.rocketseat.nlw.nearby.ui.component.market_details.NearbyMarketDetailsInfos
33 | import com.rocketseat.nlw.nearby.ui.component.market_details.NearbyMarketDetailsRules
34 | import com.rocketseat.nlw.nearby.ui.theme.Typography
35 |
36 | @Composable
37 | fun MarketDetailsScreen(
38 | modifier: Modifier = Modifier,
39 | uiState: MarketDetailsUiState,
40 | onEvent: (MarketDetailsUiEvent) -> Unit,
41 | onNavigateToQRCodeScanner: () -> Unit,
42 | market: Market, onNavigateBack: () -> Unit
43 | ) {
44 |
45 | LaunchedEffect(true) {
46 | onEvent(MarketDetailsUiEvent.OnFetchRules(marketId = market.id))
47 | }
48 |
49 | Box(
50 | modifier = modifier.fillMaxSize()
51 | ) {
52 | AsyncImage(
53 | modifier = Modifier
54 | .fillMaxWidth()
55 | .fillMaxHeight(0.3f),
56 | contentDescription = "Imagem do Local",
57 | contentScale = ContentScale.Crop,
58 | model = market.cover
59 | )
60 |
61 | Box(
62 | modifier = Modifier
63 | .clip(RoundedCornerShape(topEnd = 16.dp, topStart = 16.dp))
64 | .fillMaxWidth()
65 | .fillMaxHeight(0.75f)
66 | .align(Alignment.BottomCenter)
67 | .background(Color.White)
68 | ) {
69 | Column(
70 | modifier = Modifier
71 | .fillMaxHeight()
72 | .padding(36.dp)
73 | ) {
74 | Column {
75 | Text(text = market.name, style = Typography.headlineLarge)
76 | Spacer(modifier = Modifier.height(16.dp))
77 | Text(text = market.description, style = Typography.bodyLarge)
78 | }
79 | Spacer(modifier = Modifier.height(48.dp))
80 | Column(
81 | modifier = Modifier
82 | .weight(1f)
83 | .verticalScroll(rememberScrollState())
84 | ) {
85 | NearbyMarketDetailsInfos(market = market)
86 | HorizontalDivider(
87 | modifier = Modifier
88 | .fillMaxWidth()
89 | .padding(vertical = 24.dp)
90 | )
91 | if (!uiState.rules.isNullOrEmpty()) {
92 | NearbyMarketDetailsRules(rules = uiState.rules)
93 | HorizontalDivider(
94 | modifier = Modifier
95 | .fillMaxWidth()
96 | .padding(vertical = 24.dp)
97 | )
98 | }
99 | if (!uiState.coupon.isNullOrEmpty())
100 | NearbyMarketDetailsCoupons(coupons = listOf(uiState.coupon))
101 | }
102 |
103 | NearbyButton(
104 | modifier = Modifier
105 | .fillMaxWidth()
106 | .padding(top = 24.dp),
107 | text = "Ler QR Code",
108 | onClick = onNavigateToQRCodeScanner
109 | )
110 | }
111 | }
112 |
113 | NearbyButton(
114 | modifier = Modifier
115 | .align(Alignment.TopStart)
116 | .padding(24.dp),
117 | iconRes = R.drawable.ic_arrow_left,
118 | onClick = onNavigateBack
119 | )
120 | }
121 | }
122 |
123 | @Preview
124 | @Composable
125 | private fun MarketDetailsScreenPreview() {
126 | MarketDetailsScreen(
127 | market = mockMarkets.first(),
128 | uiState = MarketDetailsUiState(),
129 | onEvent = {},
130 | onNavigateToQRCodeScanner = {},
131 | onNavigateBack = {}
132 | )
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/rocketseat/nlw/nearby/ui/component/home/NearbyGoogleMap.kt:
--------------------------------------------------------------------------------
1 | package com.rocketseat.nlw.nearby.ui.component.home
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.rememberCoroutineScope
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.platform.LocalContext
11 | import androidx.compose.ui.platform.LocalDensity
12 | import androidx.compose.ui.unit.dp
13 | import androidx.core.graphics.drawable.toBitmap
14 | import com.google.android.gms.maps.CameraUpdateFactory
15 | import com.google.android.gms.maps.model.BitmapDescriptorFactory
16 | import com.google.android.gms.maps.model.CameraPosition
17 | import com.google.android.gms.maps.model.LatLng
18 | import com.google.maps.android.compose.GoogleMap
19 | import com.google.maps.android.compose.MapUiSettings
20 | import com.google.maps.android.compose.Marker
21 | import com.google.maps.android.compose.MarkerState
22 | import com.google.maps.android.compose.rememberCameraPositionState
23 | import com.rocketseat.nlw.nearby.R
24 | import com.rocketseat.nlw.nearby.data.model.mock.mockUserLocation
25 | import com.rocketseat.nlw.nearby.ui.screen.home.HomeUiState
26 | import com.rocketseat.nlw.nearby.ui.util.findNortheastPoint
27 | import com.rocketseat.nlw.nearby.ui.util.findSouthwestPoint
28 | import kotlinx.coroutines.delay
29 | import kotlinx.coroutines.launch
30 | import okhttp3.internal.toImmutableList
31 | import kotlin.collections.orEmpty
32 | import kotlin.math.roundToInt
33 |
34 | @Composable
35 | fun NearbyGoogleMap(modifier: Modifier = Modifier, uiState: HomeUiState) {
36 | val context = LocalContext.current
37 | val coroutineScope = rememberCoroutineScope()
38 | val density = LocalDensity.current
39 |
40 | val cameraPositionState = rememberCameraPositionState() {
41 | position = CameraPosition.fromLatLngZoom(mockUserLocation, 13f)
42 | }
43 |
44 | val uiSettings by remember {
45 | mutableStateOf(MapUiSettings(zoomControlsEnabled = true))
46 | }
47 |
48 | GoogleMap(
49 | modifier = modifier.fillMaxSize(),
50 | cameraPositionState = cameraPositionState,
51 | uiSettings = uiSettings
52 | ) {
53 | context.getDrawable(R.drawable.ic_user_location)?.let {
54 | Marker(
55 | state = MarkerState(position = mockUserLocation),
56 | icon = BitmapDescriptorFactory.fromBitmap(
57 | it.toBitmap(
58 | width = density.run { 72.dp.toPx() }.roundToInt(),
59 | height = density.run { 72.dp.toPx() }.roundToInt()
60 | )
61 | )
62 | )
63 | }
64 |
65 | if (!uiState.markets.isNullOrEmpty()) {
66 | context.getDrawable(R.drawable.img_pin)?.let {
67 | uiState.marketLocations?.toImmutableList()
68 | ?.forEachIndexed { index, location ->
69 | Marker(
70 | state = MarkerState(position = location),
71 | icon = BitmapDescriptorFactory.fromBitmap(
72 | it.toBitmap(
73 | width = density.run { 36.dp.toPx() }
74 | .roundToInt(),
75 | height = density.run { 36.dp.toPx() }
76 | .roundToInt()
77 | )
78 | ),
79 | title = uiState.markets[index].name
80 | )
81 | }.also {
82 | coroutineScope.launch {
83 | val allMarks = uiState.marketLocations?.plus(
84 | mockUserLocation
85 | )
86 |
87 | val southwestPoint =
88 | findSouthwestPoint(points = allMarks.orEmpty())
89 | val northeastPoint =
90 | findNortheastPoint(points = allMarks.orEmpty())
91 |
92 | val centerPointLatitude =
93 | (southwestPoint.latitude + northeastPoint.latitude) / 2
94 | val centerPointLongitude =
95 | (southwestPoint.longitude + northeastPoint.longitude) / 2
96 |
97 | val cameraUpdate =
98 | CameraUpdateFactory.newCameraPosition(
99 | CameraPosition(
100 | LatLng(
101 | centerPointLatitude,
102 | centerPointLongitude
103 | ),
104 | 13f,
105 | 0f,
106 | 0f
107 | )
108 | )
109 | delay(200)
110 | cameraPositionState.animate(
111 | cameraUpdate,
112 | durationMs = 500
113 | )
114 | }
115 | }
116 | }
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_logo_logo_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or 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 UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bakery.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_splash_screen.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
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 |
175 |
180 |
185 |
190 |
195 |
196 |
197 |
--------------------------------------------------------------------------------