Skip to content

Commit a22a0b3

Browse files
authored
Merge pull request #79 from woowacourse-teams/feature/#74
[feat] μœ νŠœλ²„ μ—¬ν–‰ 일정 상세 νŽ˜μ΄μ§€ 일차별 μž₯μ†Œλ“€ 보여쀄 View κ΅¬ν˜„
2 parents f4bfe5c + b9adecb commit a22a0b3

10 files changed

Lines changed: 209 additions & 20 deletions

File tree

β€Žandroid/app/src/main/java/com/on/turip/ui/common/ImageExtensions.ktβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.widget.ImageView
44
import coil.load
55
import coil.transform.CircleCropTransformation
66

7-
fun ImageView.loadByCircle(imageUrl: String) {
7+
fun ImageView.loadCircularImage(imageUrl: String) {
88
this.load(imageUrl) {
99
crossfade(true)
1010
transformations(CircleCropTransformation())

β€Žandroid/app/src/main/java/com/on/turip/ui/search/result/SearchResultViewHolder.ktβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
66
import com.on.turip.R
77
import com.on.turip.databinding.ItemSearchResultBinding
88
import com.on.turip.domain.contents.VideoInformation
9-
import com.on.turip.ui.common.loadByCircle
9+
import com.on.turip.ui.common.loadCircularImage
1010

1111
class SearchResultViewHolder(
1212
private val binding: ItemSearchResultBinding,
@@ -37,7 +37,7 @@ class SearchResultViewHolder(
3737
videoInformation.content.creator.channelName,
3838
videoInformation.content.uploadedDate,
3939
)
40-
binding.ivSearchResultThumbnail.loadByCircle(videoInformation.content.creator.profileImage)
40+
binding.ivSearchResultThumbnail.loadCircularImage(videoInformation.content.creator.profileImage)
4141
}
4242

4343
companion object {

β€Žandroid/app/src/main/java/com/on/turip/ui/travel/detail/DayModel.ktβ€Ž

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.on.turip.ui.travel.detail
22

33
data class DayModel(
44
val day: Int,
5-
val isSelected: Boolean,
6-
)
5+
val isSelected: Boolean = false,
6+
) {
7+
fun isSame(dayModel: DayModel): Boolean = this == dayModel
8+
}
79

810
fun Int.initDayModels(): List<DayModel> = (1..this).map { it.initDayModel() }
911

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.on.turip.ui.travel.detail
2+
3+
import android.net.Uri
4+
import androidx.core.net.toUri
5+
6+
data class PlaceModel(
7+
val name: String,
8+
val category: String,
9+
val mapLink: String,
10+
) {
11+
val placeUri: Uri
12+
get() = mapLink.toUri()
13+
}

β€Žandroid/app/src/main/java/com/on/turip/ui/travel/detail/TravelDetailActivity.ktβ€Ž

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,18 @@ class TravelDetailActivity : BaseActivity<TravelDetailViewModel, ActivitySearchD
3030
)
3131
}
3232

33-
private val travelDayAdapter: TravelDayAdapter =
33+
private val travelDayAdapter by lazy {
3434
TravelDayAdapter { dayModel ->
35-
viewModel.selectDay(dayModel)
35+
viewModel.updateDay(dayModel)
3636
}
37+
}
38+
39+
private val travelPlaceAdapter by lazy {
40+
TravelPlaceAdapter { placeModel ->
41+
val intent = Intent(Intent.ACTION_VIEW, placeModel.placeUri)
42+
startActivity(intent)
43+
}
44+
}
3745

3846
private fun enableFullscreen() {
3947
WindowCompat.setDecorFitsSystemWindows(this.window, false)
@@ -67,6 +75,7 @@ class TravelDetailActivity : BaseActivity<TravelDetailViewModel, ActivitySearchD
6775
super.onCreate(savedInstanceState)
6876

6977
setupToolbar()
78+
setupOnBackPressedDispatcher()
7079
setupWebView()
7180
setupAdapters()
7281
setupListeners()
@@ -75,9 +84,13 @@ class TravelDetailActivity : BaseActivity<TravelDetailViewModel, ActivitySearchD
7584

7685
private fun setupToolbar() {
7786
setSupportActionBar(binding.tbSearchDetail)
78-
supportActionBar?.setDisplayHomeAsUpEnabled(true)
79-
supportActionBar?.title = null
87+
supportActionBar?.apply {
88+
setDisplayHomeAsUpEnabled(true)
89+
title = null
90+
}
91+
}
8092

93+
private fun setupOnBackPressedDispatcher() {
8194
onBackPressedDispatcher.addCallback(
8295
this,
8396
object : OnBackPressedCallback(true) {
@@ -117,6 +130,10 @@ class TravelDetailActivity : BaseActivity<TravelDetailViewModel, ActivitySearchD
117130

118131
private fun setupAdapters() {
119132
binding.rvSearchDetailTravelDay.adapter = travelDayAdapter
133+
binding.rvSearchDetailTravelPlace.apply {
134+
adapter = travelPlaceAdapter
135+
itemAnimator = null
136+
}
120137
}
121138

122139
private fun setupListeners() {
@@ -134,6 +151,9 @@ class TravelDetailActivity : BaseActivity<TravelDetailViewModel, ActivitySearchD
134151
viewModel.days.observe(this) { dayModels ->
135152
travelDayAdapter.submitList(dayModels)
136153
}
154+
viewModel.places.observe(this) { placeModels ->
155+
travelPlaceAdapter.submitList(placeModels)
156+
}
137157
}
138158

139159
override fun onDestroy() {

β€Žandroid/app/src/main/java/com/on/turip/ui/travel/detail/TravelDetailViewModel.ktβ€Ž

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,32 @@ import androidx.lifecycle.MutableLiveData
55
import androidx.lifecycle.ViewModel
66

77
class TravelDetailViewModel : ViewModel() {
8-
private val _days: MutableLiveData<List<DayModel>> = MutableLiveData()
9-
val days: LiveData<List<DayModel>> = _days
8+
private val _days: MutableLiveData<List<DayModel>> = MutableLiveData(emptyList())
9+
val days: LiveData<List<DayModel>> get() = _days
10+
11+
private val _places: MutableLiveData<List<PlaceModel>> = MutableLiveData(emptyList())
12+
val places: LiveData<List<PlaceModel>> get() = _places
13+
14+
private var placeCacheByDay: Map<DayModel, List<PlaceModel>>
1015

1116
init {
12-
val loadDay: Int = 10 // TODO: μ„œλ²„μ—μ„œ λ°›μ•„μ˜¨ 여행일정 X일을 μΆ”μΆœν•΄μ„œ μΆ”κ°€
17+
val loadDay: Int = 3 // TODO: μ„œλ²„μ—μ„œ λ°›μ•„μ˜¨ 여행일정 X일을 μΆ”μΆœν•΄μ„œ μΆ”κ°€
1318
_days.value = loadDay.initDayModels()
19+
placeCacheByDay = emptyMap() // TODO: μ„œλ²„μ—μ„œ λ°›μ•„μ˜¨ λ°μ΄ν„°λ‘œ key:일차, value: μž₯μ†Œλ“€λ‘œ map 자료ꡬ쑰둜 캐싱
20+
_places.value = placeCacheByDay[DayModel(1)]
1421
}
1522

16-
fun selectDay(selectedDay: DayModel) {
17-
val currentList = days.value ?: return
18-
val newList =
19-
currentList.map { item ->
20-
if (item.day == selectedDay.day) {
21-
item.copy(isSelected = true)
23+
fun updateDay(day: DayModel) {
24+
val daysStatus: List<DayModel> = days.value ?: return // TODO: days.value null 일 λ•Œ 둜직 처리 ν•„μš”
25+
val updateDaysStatus =
26+
daysStatus.map { dayModel ->
27+
if (dayModel.isSame(day)) {
28+
dayModel.copy(isSelected = true)
2229
} else {
23-
item.copy(isSelected = false)
30+
dayModel.copy(isSelected = false)
2431
}
2532
}
26-
_days.value = newList
33+
_days.value = updateDaysStatus
34+
_places.value = placeCacheByDay[day]
2735
}
2836
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.on.turip.ui.travel.detail
2+
3+
import android.view.ViewGroup
4+
import androidx.recyclerview.widget.DiffUtil
5+
import androidx.recyclerview.widget.ListAdapter
6+
7+
class TravelPlaceAdapter(
8+
private val onClickListener: TravelPlaceViewHolder.OnPlaceListener,
9+
) : ListAdapter<PlaceModel, TravelPlaceViewHolder>(TravelPlaceDiffUtil) {
10+
override fun onCreateViewHolder(
11+
parent: ViewGroup,
12+
viewType: Int,
13+
): TravelPlaceViewHolder = TravelPlaceViewHolder.of(parent, onClickListener)
14+
15+
override fun onBindViewHolder(
16+
holder: TravelPlaceViewHolder,
17+
position: Int,
18+
) {
19+
val placeModel: PlaceModel = getItem(position)
20+
holder.bind(placeModel)
21+
}
22+
23+
private object TravelPlaceDiffUtil : DiffUtil.ItemCallback<PlaceModel>() {
24+
override fun areItemsTheSame(
25+
oldItem: PlaceModel,
26+
newItem: PlaceModel,
27+
): Boolean = oldItem.name == newItem.name
28+
29+
override fun areContentsTheSame(
30+
oldItem: PlaceModel,
31+
newItem: PlaceModel,
32+
): Boolean = oldItem == newItem
33+
}
34+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.on.turip.ui.travel.detail
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.recyclerview.widget.RecyclerView
6+
import com.on.turip.databinding.ItemTravelPlaceBinding
7+
8+
class TravelPlaceViewHolder(
9+
private val binding: ItemTravelPlaceBinding,
10+
onClickListener: OnPlaceListener,
11+
) : RecyclerView.ViewHolder(binding.root) {
12+
private var placeModel: PlaceModel? = null
13+
14+
init {
15+
binding.ivTravelPlaceLink.setOnClickListener {
16+
placeModel?.let {
17+
onClickListener.onPlaceClick(it)
18+
}
19+
}
20+
}
21+
22+
fun bind(placeModel: PlaceModel) {
23+
this.placeModel = placeModel
24+
binding.tvTravelPlaceName.text = placeModel.name
25+
binding.tvTravelPlaceCategory.text = placeModel.category
26+
}
27+
28+
companion object {
29+
fun of(
30+
parent: ViewGroup,
31+
onClickListener: OnPlaceListener,
32+
): TravelPlaceViewHolder {
33+
val inflater: LayoutInflater = LayoutInflater.from(parent.context)
34+
val binding: ItemTravelPlaceBinding =
35+
ItemTravelPlaceBinding.inflate(inflater, parent, false)
36+
return TravelPlaceViewHolder(binding, onClickListener)
37+
}
38+
}
39+
40+
fun interface OnPlaceListener {
41+
fun onPlaceClick(placeModel: PlaceModel)
42+
}
43+
}

β€Žandroid/app/src/main/res/layout/activity_search_detail.xmlβ€Ž

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,18 @@
242242
app:layout_constraintTop_toBottomOf="@id/rv_search_detail_travel_day"
243243
tools:text="일정 4개" />
244244

245+
<androidx.recyclerview.widget.RecyclerView
246+
android:id="@+id/rv_search_detail_travel_place"
247+
android:layout_width="match_parent"
248+
android:layout_height="0dp"
249+
android:layout_marginTop="12dp"
250+
android:orientation="vertical"
251+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
252+
app:layout_constraintEnd_toEndOf="parent"
253+
app:layout_constraintStart_toStartOf="parent"
254+
app:layout_constraintTop_toBottomOf="@id/tv_search_detail_day_place_count"
255+
tools:listitem="@layout/item_travel_place" />
256+
245257
</androidx.constraintlayout.widget.ConstraintLayout>
246258
</androidx.core.widget.NestedScrollView>
247259
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/cl_travel_place"
6+
android:layout_width="match_parent"
7+
android:layout_height="wrap_content"
8+
android:layout_marginHorizontal="20dp"
9+
android:layout_marginBottom="12dp"
10+
android:background="@drawable/bg_stroke_gray200_radius_24dp"
11+
android:backgroundTint="@color/gray_300_5b5b5b">
12+
13+
<TextView
14+
android:id="@+id/tv_travel_place_name"
15+
android:layout_width="0dp"
16+
android:layout_height="wrap_content"
17+
android:layout_marginHorizontal="28dp"
18+
android:layout_marginTop="20dp"
19+
android:ellipsize="end"
20+
android:maxLines="1"
21+
android:textAppearance="@style/title1"
22+
android:textColor="@color/pure_white_ffffff"
23+
app:layout_constraintEnd_toStartOf="@id/iv_travel_place_link"
24+
app:layout_constraintStart_toStartOf="parent"
25+
app:layout_constraintTop_toTopOf="parent"
26+
tools:text="κ²½ν¬λŒ€ ν•΄μˆ˜μš•μž₯κ²½ν¬λŒ€ ν•΄μˆ˜μš•μž₯κ²½ν¬λŒ€ ν•΄μˆ˜μš•μž₯κ²½ν¬λŒ€ ν•΄μˆ˜μš•μž₯" />
27+
28+
<TextView
29+
android:id="@+id/tv_travel_place_category"
30+
android:layout_width="wrap_content"
31+
android:layout_height="0dp"
32+
android:layout_marginTop="10dp"
33+
android:textAppearance="@style/title3"
34+
android:textColor="@color/pure_white_ffffff"
35+
app:layout_constraintStart_toStartOf="@id/tv_travel_place_name"
36+
app:layout_constraintTop_toBottomOf="@id/tv_travel_place_name"
37+
tools:text="κ΄€κ΄‘μ§€" />
38+
39+
<ImageView
40+
android:id="@+id/iv_travel_place_link"
41+
android:layout_width="wrap_content"
42+
android:layout_height="0dp"
43+
android:contentDescription="@null"
44+
android:padding="15dp"
45+
android:src="@drawable/btn_map_link"
46+
app:layout_constraintBottom_toBottomOf="@id/tv_travel_place_category"
47+
app:layout_constraintDimensionRatio="1:1"
48+
app:layout_constraintEnd_toEndOf="parent"
49+
app:layout_constraintTop_toTopOf="@id/tv_travel_place_name" />
50+
51+
<View
52+
android:layout_width="match_parent"
53+
android:layout_height="23dp"
54+
app:layout_constraintStart_toStartOf="@id/tv_travel_place_category"
55+
app:layout_constraintTop_toBottomOf="@id/tv_travel_place_category" />
56+
57+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
Β (0)