Skip to content

Commit 1ce9c71

Browse files
committed
Implement api service
1 parent 444a212 commit 1ce9c71

File tree

15 files changed

+399
-74
lines changed

15 files changed

+399
-74
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
android:value="AIzaSyD_-cwlXX7BjiwBoW4qj1IiFPjRaDtZaYI" />
2020

2121
<activity
22-
android:name=".ui.MainActivity"
22+
android:name=".screens.MainActivity"
2323
android:exported="true">
2424
<intent-filter>
2525
<action android:name="android.intent.action.MAIN" />

app/src/main/java/com/adyen/android/assignment/AdyenApplication.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import dagger.hilt.android.HiltAndroidApp
55

66
@HiltAndroidApp
77
class AdyenApplication : Application() {
8+
89
override fun onCreate() {
910
super.onCreate()
1011
}
12+
1113
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.adyen.android.assignment.datasource.remote
2+
3+
import com.adyen.android.assignment.model.response.PlacesReponse
4+
import com.adyen.android.assignment.model.request.LocationRequestModel
5+
import com.adyen.android.assignment.network.querybuilder.VenueRecommendationsQueryBuilder
6+
import com.adyen.android.assignment.network.service.PlacesService
7+
import com.adyen.android.assignment.network.util.NetworkResult
8+
import javax.inject.Inject
9+
10+
class PlacesRemoteData @Inject constructor(private val remoteService: PlacesService) {
11+
12+
suspend fun getPlaces(requestModel: LocationRequestModel): NetworkResult<PlacesReponse> {
13+
return remoteService.getVenueRecommendationPlaces(
14+
VenueRecommendationsQueryBuilder()
15+
.setLatitudeLongitude(
16+
latitude = requestModel.latitude,
17+
longitude = requestModel.longitude
18+
).build()
19+
)
20+
}
21+
22+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.adyen.android.assignment.model.request
2+
3+
data class LocationRequestModel(
4+
var latitude: Double,
5+
var longitude: Double
6+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.adyen.android.assignment.repository.remote
2+
3+
import com.adyen.android.assignment.datasource.remote.PlacesRemoteData
4+
import com.adyen.android.assignment.model.response.PlacesReponse
5+
import com.adyen.android.assignment.model.request.LocationRequestModel
6+
import com.adyen.android.assignment.model.response.Result
7+
import com.adyen.android.assignment.network.util.NetworkResult
8+
import javax.inject.Inject
9+
10+
class PlacesRepository @Inject constructor(private val remote: PlacesRemoteData) {
11+
12+
suspend fun getPlaces(requestModel: LocationRequestModel): NetworkResult<PlacesReponse> {
13+
return remote.getPlaces(requestModel)
14+
}
15+
16+
}

app/src/main/java/com/adyen/android/assignment/ui/MainActivity.kt renamed to app/src/main/java/com/adyen/android/assignment/screens/MainActivity.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
package com.adyen.android.assignment.ui
1+
package com.adyen.android.assignment.screens
22

3+
import android.os.Build
34
import android.os.Bundle
5+
import android.os.StrictMode
6+
import android.os.StrictMode.ThreadPolicy
47
import androidx.activity.ComponentActivity
58
import androidx.activity.compose.setContent
69
import androidx.compose.foundation.layout.fillMaxSize
@@ -9,17 +12,17 @@ import androidx.compose.material.Surface
912
import androidx.compose.ui.Modifier
1013
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
1114
import androidx.lifecycle.lifecycleScope
12-
import com.adyen.android.assignment.ui.screens.home.view.HomeScreen
15+
import com.adyen.android.assignment.screens.home.view.HomeScreen
1316
import com.adyen.android.assignment.ui.theme.AdyenApplicationTheme
1417
import dagger.hilt.android.AndroidEntryPoint
1518
import kotlinx.coroutines.delay
1619

20+
1721
@AndroidEntryPoint
1822
class MainActivity : ComponentActivity() {
1923

2024
override fun onCreate(savedInstanceState: Bundle?) {
2125
super.onCreate(savedInstanceState)
22-
2326
var keepSplashScreen = true
2427

2528
installSplashScreen().apply {
@@ -29,10 +32,8 @@ class MainActivity : ComponentActivity() {
2932
}
3033

3134
lifecycleScope.launchWhenCreated {
32-
3335
delay(2000)
3436
keepSplashScreen = false
35-
3637
setContent {
3738
AdyenApplicationTheme {
3839
Surface(
@@ -43,8 +44,8 @@ class MainActivity : ComponentActivity() {
4344
}
4445
}
4546
}
46-
4747
}
48+
4849
}
4950

5051
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package com.adyen.android.assignment.screens.home.view
2+
3+
import androidx.compose.animation.animateContentSize
4+
import androidx.compose.animation.core.Spring
5+
import androidx.compose.animation.core.spring
6+
import androidx.compose.foundation.background
7+
import androidx.compose.foundation.border
8+
import androidx.compose.foundation.layout.*
9+
import androidx.compose.foundation.lazy.LazyRow
10+
import androidx.compose.foundation.shape.RoundedCornerShape
11+
import androidx.compose.material.*
12+
import androidx.compose.material.icons.Icons
13+
import androidx.compose.material.icons.filled.*
14+
import androidx.compose.runtime.*
15+
import androidx.compose.runtime.livedata.observeAsState
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.res.dimensionResource
20+
import androidx.compose.ui.text.font.FontWeight
21+
import androidx.compose.ui.unit.dp
22+
import androidx.compose.ui.unit.sp
23+
import androidx.hilt.navigation.compose.hiltViewModel
24+
import com.adyen.android.assignment.R
25+
import com.adyen.android.assignment.model.response.Result
26+
import com.adyen.android.assignment.network.util.NetworkResult
27+
import com.adyen.android.assignment.screens.home.viewmodel.HomeViewModel
28+
import com.adyen.android.assignment.utils.Constant.GOOGLE_MAPS_CAMERA_ZOOM
29+
import com.google.android.gms.maps.model.CameraPosition
30+
import com.google.android.gms.maps.model.LatLng
31+
import com.google.maps.android.compose.GoogleMap
32+
import com.google.maps.android.compose.Marker
33+
import com.google.maps.android.compose.rememberCameraPositionState
34+
import timber.log.Timber
35+
36+
@Composable
37+
fun HomeScreen(homeViewModel: HomeViewModel = hiltViewModel()) {
38+
39+
val placesStatesList = mutableListOf<Result>()
40+
val showInfo = remember { mutableStateOf(false) }
41+
42+
LaunchedEffect(Unit) {
43+
homeViewModel.getPlaces(homeViewModel.getDummyLocationRequest())
44+
}
45+
46+
val placesState = homeViewModel.places.observeAsState()
47+
val selectedPlaceState = homeViewModel.selectedPlace.observeAsState()
48+
49+
when (val placesResponse = placesState.value) {
50+
is NetworkResult.Success -> {
51+
placesResponse.data?.results?.forEach { result ->
52+
placesStatesList.add(result)
53+
}
54+
}
55+
is NetworkResult.Failure -> {
56+
placesResponse.apply {
57+
Timber.e("$statusCode")
58+
}
59+
}
60+
else -> {}
61+
}
62+
63+
val cameraPositionState = rememberCameraPositionState {
64+
position = CameraPosition.fromLatLngZoom(
65+
homeViewModel.getDummyAmsterdamLocation(),
66+
GOOGLE_MAPS_CAMERA_ZOOM
67+
)
68+
}
69+
70+
if (placesStatesList.isNotEmpty()) {
71+
GoogleMap(
72+
modifier = Modifier.fillMaxSize(),
73+
cameraPositionState = cameraPositionState
74+
) {
75+
placesStatesList.forEach { place ->
76+
Marker(
77+
position = LatLng(
78+
place.geocodes?.main?.latitude!!,
79+
place.geocodes?.main?.longitude!!
80+
),
81+
title = place.name,
82+
snippet = place.location?.address,
83+
onClick = {
84+
homeViewModel.setSelectedPlace(place)
85+
false
86+
}
87+
)
88+
}
89+
}
90+
}
91+
92+
if (selectedPlaceState.value != null) {
93+
val place = selectedPlaceState.value
94+
place?.let {
95+
showInfo.value = true
96+
SelectedPlaceInfo(showInfo, place)
97+
}
98+
}
99+
}
100+
101+
@Composable
102+
fun SelectedPlaceInfo(showInfo: MutableState<Boolean>, place: Result) {
103+
if (showInfo.value) {
104+
Box(
105+
modifier = Modifier
106+
.fillMaxWidth(),
107+
contentAlignment = Alignment.TopCenter
108+
) {
109+
Card(
110+
backgroundColor = MaterialTheme.colors.primary,
111+
modifier = Modifier.padding(
112+
vertical = dimensionResource(id = R.dimen.place_info_card_vertical_padding),
113+
horizontal = dimensionResource(id = R.dimen.place_info_card_horizontal_padding)
114+
)
115+
) {
116+
var expanded by remember { mutableStateOf(false) }
117+
Row(
118+
modifier = Modifier
119+
.padding(dimensionResource(id = R.dimen.place_info_row_padding))
120+
.animateContentSize(
121+
animationSpec = spring(
122+
dampingRatio = Spring.DampingRatioMediumBouncy,
123+
stiffness = Spring.StiffnessLow
124+
)
125+
)
126+
) {
127+
Column(
128+
modifier = Modifier
129+
.padding(dimensionResource(id = R.dimen.place_info_column_padding))
130+
) {
131+
Row(
132+
modifier = Modifier.fillMaxWidth(),
133+
verticalAlignment = Alignment.CenterVertically,
134+
horizontalArrangement = Arrangement.SpaceBetween
135+
) {
136+
Row(Modifier.weight(3F)) {
137+
SelectedPlaceNameField(place = place)
138+
}
139+
Row(Modifier.weight(1F)) {
140+
IconButton(onClick = { expanded = !expanded }) {
141+
Icon(
142+
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
143+
contentDescription = ""
144+
)
145+
}
146+
IconButton(onClick = { showInfo.value = false }) {
147+
Icon(
148+
imageVector = Icons.Filled.Close,
149+
contentDescription = ""
150+
)
151+
}
152+
}
153+
}
154+
if (expanded) {
155+
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.place_info_spacer)))
156+
Row(modifier = Modifier.fillMaxWidth()) {
157+
SelectedPlaceAddressField(place = place)
158+
}
159+
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.place_info_spacer)))
160+
SelectedPlaceCategoriesField(place = place)
161+
}
162+
}
163+
164+
}
165+
}
166+
}
167+
}
168+
}
169+
170+
@Composable
171+
fun SelectedPlaceNameField(place: Result) {
172+
Icon(
173+
Icons.Default.Business,
174+
contentDescription = ""
175+
)
176+
Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.place_info_spacer)))
177+
Text(text = "Name : ${place.name}")
178+
}
179+
180+
@Composable
181+
fun SelectedPlaceAddressField(place: Result) {
182+
if (!place.location?.formatted_address.isNullOrEmpty()) {
183+
Icon(
184+
Icons.Default.Place,
185+
contentDescription = ""
186+
)
187+
Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.place_info_spacer)))
188+
place.location?.formatted_address?.let { formatted_address ->
189+
Text(text = "Address : $formatted_address")
190+
}
191+
}
192+
}
193+
194+
@Composable
195+
fun SelectedPlaceCategoriesField(place: Result) {
196+
Column {
197+
Row(modifier = Modifier.fillMaxWidth()) {
198+
Icon(
199+
Icons.Filled.Storefront,
200+
contentDescription = ""
201+
)
202+
Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.place_info_spacer)))
203+
Text(
204+
text = "Categories",
205+
fontSize = 16.sp
206+
)
207+
}
208+
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.place_info_spacer)))
209+
place.categories?.let { categories ->
210+
LazyRow(modifier = Modifier.fillMaxWidth()) {
211+
items(count = categories.size, itemContent = { index ->
212+
if (!categories[index].name.isNullOrEmpty()) {
213+
CategoryChip(categories[index].name!!)
214+
}
215+
})
216+
}
217+
}
218+
}
219+
}
220+
221+
@Composable
222+
fun CategoryChip(
223+
categoryName: String
224+
) {
225+
Row(
226+
horizontalArrangement = Arrangement.Center,
227+
verticalAlignment = Alignment.CenterVertically,
228+
modifier = Modifier
229+
.padding(
230+
vertical = 2.dp,
231+
horizontal = 4.dp
232+
)
233+
.border(
234+
width = 1.dp,
235+
color = Color.White,
236+
shape = RoundedCornerShape(16.dp)
237+
)
238+
.background(
239+
color = Color.Transparent,
240+
)
241+
.padding(4.dp)
242+
) {
243+
Text(
244+
text = categoryName,
245+
fontWeight = FontWeight.Bold,
246+
fontSize = 14.sp,
247+
modifier = Modifier.padding(4.dp)
248+
)
249+
}
250+
}

0 commit comments

Comments
 (0)