@@ -9,9 +9,7 @@ import androidx.compose.foundation.layout.Arrangement
99import androidx.compose.foundation.layout.Box
1010import androidx.compose.foundation.layout.Column
1111import androidx.compose.foundation.layout.Row
12- import androidx.compose.foundation.layout.RowScope
1312import androidx.compose.foundation.layout.Spacer
14- import androidx.compose.foundation.layout.aspectRatio
1513import androidx.compose.foundation.layout.fillMaxSize
1614import androidx.compose.foundation.layout.fillMaxWidth
1715import androidx.compose.foundation.layout.height
@@ -21,11 +19,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
2119import androidx.compose.foundation.lazy.LazyColumn
2220import androidx.compose.foundation.shape.CircleShape
2321import androidx.compose.foundation.shape.RoundedCornerShape
24- import androidx.compose.material3.ExperimentalMaterial3Api
25- import androidx.compose.material3.HorizontalDivider
2622import androidx.compose.material3.Icon
27- import androidx.compose.material3.SecondaryTabRow
28- import androidx.compose.material3.Tab
2923import androidx.compose.material3.Text
3024import androidx.compose.runtime.Composable
3125import androidx.compose.runtime.Immutable
@@ -51,7 +45,6 @@ import androidx.compose.ui.res.stringResource
5145import androidx.compose.ui.res.vectorResource
5246import androidx.compose.ui.text.SpanStyle
5347import androidx.compose.ui.text.buildAnnotatedString
54- import androidx.compose.ui.text.style.TextAlign
5548import androidx.compose.ui.text.style.TextOverflow
5649import androidx.compose.ui.text.withStyle
5750import androidx.compose.ui.tooling.preview.Preview
@@ -65,9 +58,10 @@ import com.yapp.ndgl.core.ui.designsystem.NDGLNonModalBottomSheet
6558import com.yapp.ndgl.core.ui.designsystem.rememberNDGLNonModalBottomSheetState
6659import com.yapp.ndgl.core.ui.theme.NDGLTheme
6760import com.yapp.ndgl.core.ui.util.dropShadow
68- import com.yapp.ndgl.core.ui.util.noRippleClickable
69- import com.yapp.ndgl.core.util.formatString
7061import com.yapp.ndgl.feature.travel.additinerary.SelectedPlaceDetail
62+ import com.yapp.ndgl.feature.travel.component.PlaceDetailTabRow
63+ import com.yapp.ndgl.feature.travel.component.PlaceInfoTab
64+ import com.yapp.ndgl.feature.travel.component.PlacePhotoTab
7165import com.yapp.ndgl.feature.travel.model.PlaceDetailTab
7266import com.yapp.ndgl.feature.travel.model.PlaceInfo
7367import com.yapp.ndgl.feature.travel.model.PlacePhoto
@@ -160,11 +154,14 @@ internal fun SearchedPlaceBottomSheet(
160154 NDGLBottomSheetDragHandle ()
161155 }
162156 }
157+
163158 Box (modifier = Modifier .weight(1f )) {
164159 val density = LocalDensity .current
165160 val thumbnailHeight = 230 .dp
166161 val navBarSectionHeight = 48 .dp
167- val maxCollapseHeightPx = with (density) { (navBarSectionHeight + thumbnailHeight).toPx() }
162+ val thumbnailHeightPx = with (density) { thumbnailHeight.toPx() }
163+ val navBarSectionHeightPx = with (density) { navBarSectionHeight.toPx() }
164+ val maxCollapseHeightPx = navBarSectionHeightPx + thumbnailHeightPx
168165
169166 var collapseOffset by remember { mutableFloatStateOf(0f ) }
170167 var selectedTab by remember { mutableStateOf(PlaceDetailTab .INFO ) }
@@ -191,10 +188,7 @@ internal fun SearchedPlaceBottomSheet(
191188 }
192189 }
193190
194- val thumbnailProgress = (
195- 1f - (collapseOffset - with (density) { navBarSectionHeight.toPx() })
196- .coerceAtLeast(0f ) / with (density) { thumbnailHeight.toPx() }
197- ).coerceIn(0f , 1f )
191+ val thumbnailProgress = (1f - (collapseOffset - navBarSectionHeightPx).coerceAtLeast(0f ) / thumbnailHeightPx).coerceIn(0f , 1f )
198192
199193 Column (
200194 modifier = Modifier
@@ -221,14 +215,13 @@ internal fun SearchedPlaceBottomSheet(
221215 )
222216 }
223217
224- SearchedPlaceDetailTabRow (selectedTab = selectedTab, onTabSelected = { selectedTab = it })
225- HorizontalDivider (thickness = 1 .dp, color = NDGLTheme .colors.black200)
218+ PlaceDetailTabRow (selectedTab = selectedTab, onTabSelected = { selectedTab = it })
226219
227220 LazyColumn (modifier = Modifier .weight(1f )) {
228221 when (selectedTab) {
229222 PlaceDetailTab .INFO -> item {
230223 Spacer (Modifier .height(24 .dp))
231- SearchedPlaceInfoTab (placeInfo, clickAddress, clickMenu)
224+ PlaceInfoTab (placeInfo, clickAddress, clickMenu)
232225 }
233226
234227 PlaceDetailTab .PHOTO -> {
@@ -245,7 +238,7 @@ internal fun SearchedPlaceBottomSheet(
245238
246239 item {
247240 Spacer (Modifier .height(20 .dp))
248- SearchedPlacePhotoTab (leftPhotos = leftPhotos, rightPhotos = rightPhotos)
241+ PlacePhotoTab (leftPhotos = leftPhotos, rightPhotos = rightPhotos)
249242 }
250243 }
251244 }
@@ -331,225 +324,26 @@ private fun SearchedPlaceInfoHeader(isExpanded: Boolean, placeInfo: PlaceInfo, b
331324 }
332325}
333326
334- @OptIn(ExperimentalMaterial3Api ::class )
335- @Composable
336- private fun SearchedPlaceDetailTabRow (
337- selectedTab : PlaceDetailTab ,
338- onTabSelected : (PlaceDetailTab ) -> Unit ,
339- ) {
340- val tabs = PlaceDetailTab .entries
341- val selectedIndex = tabs.indexOf(selectedTab)
342-
343- Box (
344- modifier = Modifier
345- .fillMaxWidth()
346- .height(48 .dp),
347- ) {
348- SecondaryTabRow (
349- selectedTabIndex = selectedIndex,
350- modifier = Modifier .fillMaxWidth(),
351- containerColor = NDGLTheme .colors.white,
352- contentColor = NDGLTheme .colors.black900,
353- indicator = {},
354- divider = {},
355- ) {
356- tabs.forEachIndexed { index, tab ->
357- val isSelected = index == selectedIndex
358- Tab (
359- selected = isSelected,
360- onClick = { onTabSelected(tab) },
361- modifier = Modifier .background(
362- if (isSelected) NDGLTheme .colors.black100 else NDGLTheme .colors.white,
363- ),
364- text = {
365- Text (
366- stringResource(tab.titleRes),
367- color = NDGLTheme .colors.black600,
368- style = NDGLTheme .typography.bodyMdSemiBold,
369- textAlign = TextAlign .Center ,
370- )
371- },
372- )
373- }
374- }
375- }
376- }
377-
378- @Composable
379- private fun SearchedPlaceInfoTab (
380- placeInfo : PlaceInfo ,
381- clickAddress : () -> Unit ,
382- clickMenu : () -> Unit ,
383- ) {
384- Column (
385- modifier = Modifier
386- .fillMaxWidth()
387- .padding(horizontal = 24 .dp)
388- .padding(top = 24 .dp, bottom = 16 .dp),
389- verticalArrangement = Arrangement .spacedBy(8 .dp),
390- ) {
391- if (placeInfo.address != null ) {
392- SearchedPlaceInfoRow (
393- iconRes = R .drawable.ic_24_pin,
394- onClick = clickAddress,
395- ) {
396- Text (
397- text = placeInfo.address,
398- style = NDGLTheme .typography.bodyMdMedium,
399- color = NDGLTheme .colors.black700,
400- modifier = Modifier .weight(1f ),
401- )
402- }
403- }
404-
405- if (placeInfo.websiteUrl != null ) {
406- SearchedPlaceInfoRow (
407- iconRes = R .drawable.ic_24_book,
408- onClick = clickMenu,
409- ) {
410- Column (
411- modifier = Modifier .weight(1f ),
412- verticalArrangement = Arrangement .spacedBy(4 .dp),
413- ) {
414- Text (
415- text = stringResource(R .string.place_detail_menu),
416- style = NDGLTheme .typography.bodyMdMedium,
417- color = NDGLTheme .colors.black700,
418- )
419- Text (
420- text = placeInfo.websiteUrl,
421- style = NDGLTheme .typography.bodyMdMedium,
422- color = NDGLTheme .colors.black500,
423- maxLines = 1 ,
424- )
425- }
426- }
427- }
428-
429- if (placeInfo.phoneNumber != null ) {
430- SearchedPlaceInfoRow (iconRes = R .drawable.ic_24_phone) {
431- Text (
432- text = placeInfo.phoneNumber,
433- style = NDGLTheme .typography.bodyMdMedium,
434- color = NDGLTheme .colors.black700,
435- modifier = Modifier .weight(1f ),
436- )
437- }
438- }
439-
440- SearchedPlaceInfoRow (iconRes = R .drawable.ic_24_clock) {
441- Text (
442- text = stringResource(
443- R .string.estimated_duration_format,
444- placeInfo.estimatedDuration.formatString(),
445- ),
446- style = NDGLTheme .typography.bodyMdMedium,
447- color = NDGLTheme .colors.black700,
448- modifier = Modifier .weight(1f ),
449- )
450- }
451- }
452- }
453-
454- @Composable
455- private fun SearchedPlaceInfoRow (
456- iconRes : Int ,
457- onClick : (() -> Unit )? = null,
458- content : @Composable RowScope .() -> Unit ,
459- ) {
460- Row (
461- modifier = Modifier
462- .fillMaxWidth()
463- .then(if (onClick != null ) Modifier .noRippleClickable { onClick() } else Modifier ),
464- horizontalArrangement = Arrangement .spacedBy(8 .dp),
465- verticalAlignment = Alignment .CenterVertically ,
466- ) {
467- Icon (
468- imageVector = ImageVector .vectorResource(iconRes),
469- contentDescription = null ,
470- tint = NDGLTheme .colors.green500,
471- modifier = Modifier .size(24 .dp),
472- )
473- content()
474- if (onClick != null ) {
475- Icon (
476- modifier = Modifier
477- .size(24 .dp)
478- .clip(CircleShape )
479- .clickable { onClick() },
480- imageVector = ImageVector .vectorResource(R .drawable.ic_24_chevron_right),
481- tint = NDGLTheme .colors.black600,
482- contentDescription = null ,
483- )
484- }
485- }
486- }
487-
488- @Composable
489- private fun SearchedPlacePhotoTab (leftPhotos : List <PlacePhoto >, rightPhotos : List <PlacePhoto >) {
490- Row (
491- modifier = Modifier
492- .fillMaxWidth()
493- .padding(horizontal = 24 .dp),
494- horizontalArrangement = Arrangement .spacedBy(8 .dp),
495- ) {
496- Column (
497- modifier = Modifier .weight(1f ),
498- verticalArrangement = Arrangement .spacedBy(8 .dp),
499- ) {
500- leftPhotos.forEach { photo ->
501- AsyncImage (
502- model = photo.url,
503- contentDescription = null ,
504- modifier = Modifier
505- .fillMaxWidth()
506- .aspectRatio(photo.aspectRatio)
507- .clip(RoundedCornerShape (8 .dp))
508- .background(Color .LightGray ),
509- contentScale = ContentScale .Crop ,
510- )
511- }
512- }
513- Column (
514- modifier = Modifier .weight(1f ),
515- verticalArrangement = Arrangement .spacedBy(8 .dp),
516- ) {
517- rightPhotos.forEach { photo ->
518- AsyncImage (
519- model = photo.url,
520- contentDescription = null ,
521- modifier = Modifier
522- .fillMaxWidth()
523- .aspectRatio(photo.aspectRatio)
524- .clip(RoundedCornerShape (8 .dp))
525- .background(Color .LightGray ),
526- contentScale = ContentScale .Crop ,
527- )
528- }
529- }
530- }
531- }
532-
533- private val previewPlaceDetail = SelectedPlaceDetail (
534- PlaceInfo (
535- name = " 콜로세움" ,
536- placeType = PlaceType .ATTRACTION ,
537- rating = 4.8 ,
538- userRatingCount = 12450 ,
539- address = " Piazza del Colosseo, 1, 00184 Roma RM, Italy" ,
540- phoneNumber = " +39 06 3996 7700" ,
541- websiteUrl = " https://www.colosseo.it" ,
542- estimatedDuration = 2 .hours,
543- priceRange = PriceRange (
544- startPrice = Price (currencyCode = " EUR" , units = " 5" , symbol = " €" ),
545- endPrice = Price (currencyCode = " EUR" , units = " 15" , symbol = " €" ),
546- ),
547- ),
548- )
549-
550327@Preview(name = " SearchedPlace - Expanded" , showBackground = true )
551328@Composable
552329private fun SearchedPlaceBottomSheet_Expanded_Preview () {
330+ val previewPlaceDetail = SelectedPlaceDetail (
331+ PlaceInfo (
332+ name = " 콜로세움" ,
333+ placeType = PlaceType .ATTRACTION ,
334+ rating = 4.8 ,
335+ userRatingCount = 12450 ,
336+ address = " Piazza del Colosseo, 1, 00184 Roma RM, Italy" ,
337+ phoneNumber = " +39 06 3996 7700" ,
338+ websiteUrl = " https://www.colosseo.it" ,
339+ estimatedDuration = 2 .hours,
340+ priceRange = PriceRange (
341+ startPrice = Price (currencyCode = " EUR" , units = " 5" , symbol = " €" ),
342+ endPrice = Price (currencyCode = " EUR" , units = " 15" , symbol = " €" ),
343+ ),
344+ ),
345+ )
346+
553347 NDGLTheme {
554348 Box (modifier = Modifier .fillMaxSize()) {
555349 SearchedPlaceBottomSheet (
@@ -568,6 +362,23 @@ private fun SearchedPlaceBottomSheet_Expanded_Preview() {
568362@Preview(name = " SearchedPlace - PartiallyExpanded" , showBackground = true )
569363@Composable
570364private fun SearchedPlaceBottomSheet_PartiallyExpanded_Preview () {
365+ val previewPlaceDetail = SelectedPlaceDetail (
366+ PlaceInfo (
367+ name = " 콜로세움" ,
368+ placeType = PlaceType .ATTRACTION ,
369+ rating = 4.8 ,
370+ userRatingCount = 12450 ,
371+ address = " Piazza del Colosseo, 1, 00184 Roma RM, Italy" ,
372+ phoneNumber = " +39 06 3996 7700" ,
373+ websiteUrl = " https://www.colosseo.it" ,
374+ estimatedDuration = 2 .hours,
375+ priceRange = PriceRange (
376+ startPrice = Price (currencyCode = " EUR" , units = " 5" , symbol = " €" ),
377+ endPrice = Price (currencyCode = " EUR" , units = " 15" , symbol = " €" ),
378+ ),
379+ ),
380+ )
381+
571382 NDGLTheme {
572383 Box (modifier = Modifier .fillMaxSize()) {
573384 SearchedPlaceBottomSheet (
0 commit comments