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