|
1 | 1 | package com.yapp.ndgl.feature.travel.placedetail |
2 | 2 |
|
| 3 | +import androidx.lifecycle.viewModelScope |
3 | 4 | import com.yapp.ndgl.core.base.BaseViewModel |
| 5 | +import com.yapp.ndgl.core.util.suspendRunCatching |
| 6 | +import com.yapp.ndgl.data.travel.repository.PlaceRepository |
| 7 | +import com.yapp.ndgl.feature.travel.model.AlternativePlace |
4 | 8 | import com.yapp.ndgl.feature.travel.model.PlaceDetailTab |
5 | 9 | import com.yapp.ndgl.feature.travel.model.PlacePhoto |
6 | | -import com.yapp.ndgl.feature.travel.model.PlaceType |
7 | 10 | import com.yapp.ndgl.feature.travel.model.Price |
8 | 11 | import com.yapp.ndgl.feature.travel.model.PriceRange |
| 12 | +import com.yapp.ndgl.feature.travel.model.TipContent |
| 13 | +import com.yapp.ndgl.feature.travel.model.toPlaceType |
| 14 | +import com.yapp.ndgl.navigation.model.RouteAlternativePlace |
| 15 | +import com.yapp.ndgl.navigation.model.RouteTipContent |
9 | 16 | import dagger.assisted.Assisted |
10 | 17 | import dagger.assisted.AssistedFactory |
11 | 18 | import dagger.assisted.AssistedInject |
12 | 19 | import dagger.hilt.android.lifecycle.HiltViewModel |
| 20 | +import kotlinx.coroutines.delay |
| 21 | +import kotlinx.coroutines.launch |
13 | 22 | import kotlin.time.Duration.Companion.hours |
14 | 23 |
|
15 | 24 | @HiltViewModel(assistedFactory = PlaceDetailViewModel.Factory::class) |
16 | 25 | class PlaceDetailViewModel @AssistedInject constructor( |
17 | 26 | @Assisted private val placeId: String, |
| 27 | + @Assisted private val tipContent: RouteTipContent?, |
| 28 | + @Assisted private val alternativePlaces: List<RouteAlternativePlace>, |
| 29 | + private val placeRepository: PlaceRepository, |
18 | 30 | ) : BaseViewModel<PlaceDetailState, PlaceDetailIntent, PlaceDetailSideEffect>( |
19 | 31 | initialState = PlaceDetailState(), |
20 | 32 | ) { |
21 | 33 | init { |
22 | | - loadPlaceData() |
| 34 | + loadPlaceDetail() |
23 | 35 | } |
24 | 36 |
|
25 | | - private fun loadPlaceData() { |
26 | | - // TODO: Load from repository (현재는 테스트를 위한 더미 데이터) |
27 | | - reduce { |
28 | | - copy( |
29 | | - placeInfo = PlaceInfo( |
30 | | - id = placeId, |
31 | | - name = "젤라테리아 파씨 (Gelateria Fassi)", |
32 | | - placeType = PlaceType.RESTAURANT, |
33 | | - priceRange = PriceRange( |
34 | | - startPrice = Price(currencyCode = "EUR", units = "5", symbol = "€"), |
35 | | - endPrice = Price(currencyCode = "EUR", units = "15", symbol = "€"), |
36 | | - ), |
37 | | - address = "Via Principe Eugenio, 65, 00185 Roma RM, Italy", |
38 | | - phoneNumber = "+39 06 446 4740", |
39 | | - openingHours = "매일 12:00 ~ 24:00", |
40 | | - googleMapsUri = "https://maps.google.com/?cid=14776686710302251978&g_mp=CiVnb29nbGUubWFwcy" + |
41 | | - "5wbGFjZXMudjEuUGxhY2VzLkdldFBsYWNlEAIYBCAA", |
42 | | - websiteUrl = "https://www.gyukatsu-motomura.com/shop/shinjukuhonten", |
43 | | - rating = 4.7, |
44 | | - userRatingCount = 12450, |
45 | | - estimatedDuration = 1.hours, |
46 | | - thumbnail = "https://images.unsplash.com/photo-1567206563064-6f60f40a2b57", |
47 | | - tipContent = TipContent( |
48 | | - creatorName = "", |
49 | | - tips = listOf( |
50 | | - "리조(쌀) 맛은 무조건 드셔보세요. 파씨의 시그니처입니다.", |
51 | | - "생크림(Panna)을 무료로 올려주니 꼭 추가해서 드세요!", |
52 | | - "매장 내부에 앉아서 먹을 수 있는 공간이 꽤 넓습니다.", |
53 | | - ), |
54 | | - ), |
55 | | - alternativePlaces = listOf( |
56 | | - AlternativePlace( |
57 | | - id = 1, |
58 | | - name = "폼피 티라미수 (Pompi Tiramisu)", |
59 | | - thumbnail = "https://images.unsplash.com/photo-1571877227200-a0d98ea607e9", |
60 | | - placeType = PlaceType.CAFE, |
61 | | - ), |
62 | | - AlternativePlace( |
63 | | - id = 2, |
64 | | - name = "지올리띠 (Giolitti)", |
65 | | - thumbnail = "https://images.unsplash.com/photo-1505394033343-43adc2f44bb2", |
66 | | - placeType = PlaceType.CAFE, |
67 | | - ), |
68 | | - AlternativePlace( |
69 | | - id = 3, |
70 | | - name = "라 로칸다 디 바코 (La Locanda di Bacco)", |
71 | | - thumbnail = "https://images.unsplash.com/photo-1555396273-367ea4eb4db5", |
72 | | - placeType = PlaceType.RESTAURANT, |
73 | | - ), |
| 37 | + private fun loadPlaceDetail() = viewModelScope.launch { |
| 38 | + suspendRunCatching { |
| 39 | + placeRepository.getPlace(placeId) |
| 40 | + }.onSuccess { response -> |
| 41 | + loadPlacePhotos() |
| 42 | + |
| 43 | + val place = response.place |
| 44 | + reduce { |
| 45 | + copy( |
| 46 | + placeInfo = PlaceInfo( |
| 47 | + id = place.id, |
| 48 | + name = place.name, |
| 49 | + placeType = place.category.toPlaceType(), |
| 50 | + priceRange = place.priceRange?.let { |
| 51 | + PriceRange( |
| 52 | + startPrice = Price( |
| 53 | + currencyCode = it.startPrice.currencyCode, |
| 54 | + units = it.startPrice.units, |
| 55 | + symbol = it.startPrice.symbol, |
| 56 | + ), |
| 57 | + endPrice = Price(currencyCode = it.endPrice.currencyCode, units = it.endPrice.units, symbol = it.endPrice.symbol), |
| 58 | + ) |
| 59 | + }, |
| 60 | + rating = place.rating, |
| 61 | + userRatingCount = place.userRatingCount, |
| 62 | + address = place.formattedAddress, |
| 63 | + phoneNumber = place.nationalPhoneNumber, |
| 64 | + openingHours = place.regularOpeningHours?.joinToString("\n"), |
| 65 | + googleMapsUri = place.googleMapsUri, |
| 66 | + websiteUrl = place.websiteUri, |
| 67 | + estimatedDuration = 1.hours, |
| 68 | + thumbnail = place.thumbnail.orEmpty(), |
| 69 | + tipContent = tipContent?.let { TipContent(creatorName = it.creatorName, tips = it.tips) }, |
| 70 | + alternativePlaces = alternativePlaces.map { routePlace -> |
| 71 | + AlternativePlace( |
| 72 | + id = routePlace.id, |
| 73 | + name = routePlace.name, |
| 74 | + thumbnail = routePlace.thumbnail, |
| 75 | + placeType = routePlace.placeType.toPlaceType(), |
| 76 | + ) |
| 77 | + }, |
| 78 | + latitude = place.location.latitude, |
| 79 | + longitude = place.location.longitude, |
74 | 80 | ), |
75 | | - latitude = 41.9028, |
76 | | - longitude = 12.4964, |
77 | | - ), |
78 | | - photos = listOf( |
79 | | - PlacePhoto(url = "https://picsum.photos/id/10/400/600", width = 400, height = 600), |
80 | | - PlacePhoto(url = "https://picsum.photos/id/20/600/400", width = 600, height = 400), |
81 | | - PlacePhoto(url = "https://picsum.photos/id/30/400/400", width = 400, height = 400), |
82 | | - PlacePhoto(url = "https://picsum.photos/id/40/400/500", width = 400, height = 500), |
83 | | - PlacePhoto(url = "https://picsum.photos/id/50/500/400", width = 500, height = 400), |
84 | | - PlacePhoto(url = "https://picsum.photos/id/60/400/300", width = 400, height = 300), |
85 | | - PlacePhoto(url = "https://picsum.photos/id/70/300/400", width = 300, height = 400), |
86 | | - PlacePhoto(url = "https://picsum.photos/id/80/400/450", width = 400, height = 450), |
87 | | - ), |
88 | | - ) |
| 81 | + ) |
| 82 | + } |
| 83 | + }.onFailure { |
| 84 | + // TODO: 에러 처리 |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + private fun loadPlacePhotos() = viewModelScope.launch { |
| 89 | + repeat(3) { index -> |
| 90 | + delay(1000) |
| 91 | + val result = suspendRunCatching { placeRepository.getPlacePhotos(placeId) } |
| 92 | + val photos = result.getOrNull()?.photos |
| 93 | + |
| 94 | + if (!photos.isNullOrEmpty()) { |
| 95 | + reduce { |
| 96 | + copy( |
| 97 | + photos = photos.map { PlacePhoto(url = it.photoUri, width = it.widthPx, height = it.heightPx) }, |
| 98 | + ) |
| 99 | + } |
| 100 | + return@launch |
| 101 | + } |
| 102 | + |
| 103 | + result.onFailure { |
| 104 | + // FIXME: 에러 뷰 |
| 105 | + } |
89 | 106 | } |
90 | 107 | } |
91 | 108 |
|
@@ -131,6 +148,10 @@ class PlaceDetailViewModel @AssistedInject constructor( |
131 | 148 |
|
132 | 149 | @AssistedFactory |
133 | 150 | interface Factory { |
134 | | - fun create(placeId: String): PlaceDetailViewModel |
| 151 | + fun create( |
| 152 | + placeId: String, |
| 153 | + tipContent: RouteTipContent?, |
| 154 | + alternativePlaces: List<RouteAlternativePlace>, |
| 155 | + ): PlaceDetailViewModel |
135 | 156 | } |
136 | 157 | } |
0 commit comments