Skip to content

Commit

Permalink
ui: 방문 기록, 방문 기록 생성, 방문 기록 수정 화면 구현 #52 (#74)
Browse files Browse the repository at this point in the history
* ui: typography.body textSize 1sp 씩 증가

* feat: DeleteDialogFragment에 Handler 추가

* feat: 툴바의 수정, 삭제 버튼 제어를 위한 ToolbarHandler 추가

* feat: 방문 상세 화면을 위한 VisitDetailUiModel 추가

* ui: PlaceHolder를 위한 xml 파일 추가

* feat: 방문 기록 상세 화면을 위한 VisitAdapter 및 VisitViewHolder 구현

* feat: 임시 VisitViewModel와 VisitViewModelFactory 추가

* feat: VisitFragment 화면 구현

* feat: 방문 기록에 해당하는 여행 선택을 위한 TravelSelectionFragment 구현

* feat: 방문 기록에 해당하는 날짜 선택을 위한 VisitedAtSelectionFragment 구현

* feat: 방문 기록 생성을 위한 VisitCreationActivity 구현

* feat: 방문 기록 수정을 위한 VisitUpdateActivity 구현

* refactor: DialogHandler를 DeleteDialogFragment의 생성자에서 받도록 수정

* refactor: initVisitUpdateDoneButton 중복 로직 제거

* refactor: VisitViewHolderType 메서드 명 변경 of -> from

* refactor: tv_place_name_title을 xml id convention에 맞게 수정
  • Loading branch information
s6m1n authored Jul 25, 2024
1 parent 8bf48a0 commit cde30c6
Show file tree
Hide file tree
Showing 35 changed files with 1,545 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.woowacourse.staccato.databinding.FragmentDeleteDialogBinding

class DeleteDialogFragment : DialogFragment() {
class DeleteDialogFragment(private val dialogHandler: DialogHandler) : DialogFragment() {
private var _binding: FragmentDeleteDialogBinding? = null
private val binding get() = _binding!!

Expand All @@ -29,9 +29,20 @@ class DeleteDialogFragment : DialogFragment() {
savedInstanceState: Bundle?,
): View {
_binding = FragmentDeleteDialogBinding.inflate(inflater, container, false)
initButtonClickListener()
return binding.root
}

private fun initButtonClickListener() {
binding.btnDeleteCancel.setOnClickListener {
dismiss()
}
binding.btnDeleteConfirm.setOnClickListener {
dialogHandler.onConfirmClicked()
dismiss()
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand All @@ -41,3 +52,7 @@ class DeleteDialogFragment : DialogFragment() {
const val TAG = "DeleteDialogFragment"
}
}

fun interface DialogHandler {
fun onConfirmClicked()
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package com.woowacourse.staccato.presentation

import android.graphics.drawable.Drawable
import android.widget.Button
import android.widget.ImageView
import android.widget.NumberPicker
import android.widget.TextView
import androidx.core.view.isGone
import androidx.databinding.BindingAdapter
import coil.load
import coil.transform.RoundedCornersTransformation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.woowacourse.staccato.R
import com.woowacourse.staccato.presentation.visitcreation.model.TravelUiModel

@BindingAdapter(
value = ["coilImageUrl", "coilPlaceHolder"],
Expand Down Expand Up @@ -97,3 +103,68 @@ fun ImageView.setRoundedCornerImageWithGlide(
.error(placeHolder)
.into(this)
}

@BindingAdapter("bindSetSelectedTravel")
fun TextView.setSelectedTravel(selectedTravel: TravelUiModel?) {
if (selectedTravel == null) {
text = resources.getString(R.string.visit_creation_travel_selection_hint)
setTextColor(resources.getColor(R.color.gray3, null))
} else {
text = selectedTravel.travelTitle
setTextColor(resources.getColor(R.color.staccato_black, null))
}
}

@BindingAdapter("bindSetSelectedVisitedAt")
fun TextView.setSelectedVisitedAt(selectedVisitedAt: String?) {
if (selectedVisitedAt == null) {
text = resources.getString(R.string.visit_creation_visited_at_hint)
setTextColor(resources.getColor(R.color.gray3, null))
} else {
text = selectedVisitedAt
setTextColor(resources.getColor(R.color.staccato_black, null))
}
}

@BindingAdapter(
value = ["selectedTravel", "visitedAt"],
)
fun Button.setVisitUpdateButtonActive(
travel: TravelUiModel?,
visitedAt: String?,
) {
isEnabled =
if (travel == null || visitedAt == null) {
setTextColor(resources.getColor(R.color.gray4, null))
false
} else {
setTextColor(resources.getColor(R.color.white, null))
true
}
}

@BindingAdapter("bindSetVisitedAtConfirmButtonActive")
fun Button.setVisitedAtConfirmButtonActive(items: List<String>?) {
isEnabled =
if (items.isNullOrEmpty()) {
setTextColor(resources.getColor(R.color.gray4, null))
false
} else {
setTextColor(resources.getColor(R.color.white, null))
true
}
}

@BindingAdapter("bindSetVisitedAtNumberPickerItems")
fun NumberPicker.setVisitedAtNumberPickerItems(items: List<String>?) {
if (items.isNullOrEmpty()) {
isGone = true
} else {
displayedValues = items.toTypedArray()
}
}

@BindingAdapter("bindSetVisitedAtIsEmptyVisibility")
fun TextView.setVisitedAtIsEmptyVisibility(items: List<String>?) {
isGone = !items.isNullOrEmpty()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.woowacourse.staccato.presentation

interface ToolbarHandler {
fun onUpdateClicked()

fun onDeleteClicked()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,68 @@ package com.woowacourse.staccato.presentation.visit

import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.woowacourse.staccato.DeleteDialogFragment
import com.woowacourse.staccato.R
import com.woowacourse.staccato.databinding.FragmentVisitBinding
import com.woowacourse.staccato.presentation.ToolbarHandler
import com.woowacourse.staccato.presentation.base.BindingFragment
import com.woowacourse.staccato.presentation.main.MainActivity
import com.woowacourse.staccato.presentation.visit.adapter.VisitAdapter
import com.woowacourse.staccato.presentation.visit.viewmodel.VisitViewModel
import com.woowacourse.staccato.presentation.visit.viewmodel.VisitViewModelFactory
import com.woowacourse.staccato.presentation.visitupdate.VisitUpdateActivity

class VisitFragment :
BindingFragment<FragmentVisitBinding>(R.layout.fragment_visit) {
BindingFragment<FragmentVisitBinding>(R.layout.fragment_visit), ToolbarHandler {
private val viewModel: VisitViewModel by viewModels { VisitViewModelFactory() }
private lateinit var visitAdapter: VisitAdapter
private val deleteDialog = DeleteDialogFragment { findNavController().popBackStack() }

override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
binding.btnVisitUpdate.setOnClickListener {
val visitUpdateLauncher = (activity as MainActivity).visitUpdateLauncher
VisitUpdateActivity.startWithResultLauncher(
this.requireActivity(),
visitUpdateLauncher,
)
// findNavController().navigate(R.id.action_travelFragment_to_travelUpdateFragment)
initAdapter()
initToolbarHandler()
observeData()
viewModel.fetchVisitDetailData()
}

private fun initAdapter() {
visitAdapter = VisitAdapter(mutableListOf())
binding.rvVisitDetail.adapter = visitAdapter
}

private fun initToolbarHandler() {
binding.toolbarHandler = this
binding.includeVisitToolbar.toolbarDetail.setNavigationOnClickListener {
findNavController().popBackStack()
}
}

private fun observeData() {
viewModel.visitDefault.observe(viewLifecycleOwner) { visitDefault ->
visitAdapter.updateVisitDefault(visitDefault)
}
viewModel.visitLogs.observe(viewLifecycleOwner) { visitLogs ->
visitAdapter.updateVisitLogs(visitLogs)
}
}

override fun onDeleteClicked() {
val fragmentManager = parentFragmentManager
deleteDialog.apply {
show(fragmentManager, DeleteDialogFragment.TAG)
}
}

override fun onUpdateClicked() {
val visitUpdateLauncher = (activity as MainActivity).visitUpdateLauncher
VisitUpdateActivity.startWithResultLauncher(
this.requireActivity(),
visitUpdateLauncher,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.woowacourse.staccato.presentation.visit.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.woowacourse.staccato.databinding.ItemMyVisitLogBinding
import com.woowacourse.staccato.databinding.ItemVisitDefaultBinding
import com.woowacourse.staccato.presentation.visit.model.VisitDetailUiModel

class VisitAdapter(private val items: MutableList<VisitDetailUiModel> = mutableListOf()) :
RecyclerView.Adapter<VisitViewHolder>() {
override fun getItemCount(): Int = items.size

override fun getItemViewType(position: Int): Int {
return if (position == VISIT_DEFAULT_POSITION) {
VisitViewHolderType.VISIT_DEFAULT.value
} else {
VisitViewHolderType.MY_VISIT_LOG.value
}
}

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): VisitViewHolder {
return when (VisitViewHolderType.from(viewType)) {
VisitViewHolderType.VISIT_DEFAULT -> {
val inflater = LayoutInflater.from(parent.context)
val binding = ItemVisitDefaultBinding.inflate(inflater, parent, false)
VisitViewHolder.VisitDefaultViewHolder(binding)
}

VisitViewHolderType.MY_VISIT_LOG -> {
val inflater = LayoutInflater.from(parent.context)
val binding = ItemMyVisitLogBinding.inflate(inflater, parent, false)
VisitViewHolder.MyVisitLogViewHolder(binding)
}
}
}

override fun onBindViewHolder(
holder: VisitViewHolder,
position: Int,
) {
if (holder is VisitViewHolder.VisitDefaultViewHolder) {
holder.bind(items[position] as VisitDetailUiModel.VisitDefaultUiModel)
}
if (holder is VisitViewHolder.MyVisitLogViewHolder) {
holder.bind(items[position] as VisitDetailUiModel.VisitLogUiModel)
}
}

fun updateVisitDefault(newVisitDefault: VisitDetailUiModel.VisitDefaultUiModel) {
val result = mutableListOf<VisitDetailUiModel>(newVisitDefault)
result.addAll(items.drop(VISIT_DEFAULT_ITEM_SIZE))
replaceAllItems(result)
notifyItemChanged(VISIT_DEFAULT_POSITION)
}

fun updateVisitLogs(newVisitLogs: List<VisitDetailUiModel.VisitLogUiModel>) {
val result = items.take(VISIT_DEFAULT_ITEM_SIZE).toMutableList()
result.addAll(newVisitLogs)
replaceAllItems(result)
notifyItemRangeInserted(VISIT_DEFAULT_ITEM_SIZE, result.size)
}

private fun replaceAllItems(result: MutableList<VisitDetailUiModel>) {
items.clear()
items.addAll(result)
}

companion object {
private const val VISIT_DEFAULT_POSITION = 0
private const val VISIT_DEFAULT_ITEM_SIZE = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.woowacourse.staccato.presentation.visit.adapter

import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.woowacourse.staccato.databinding.ItemMyVisitLogBinding
import com.woowacourse.staccato.databinding.ItemVisitDefaultBinding
import com.woowacourse.staccato.presentation.visit.model.VisitDetailUiModel

sealed class VisitViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
class VisitDefaultViewHolder(private val binding: ItemVisitDefaultBinding) :
VisitViewHolder(binding) {
fun bind(item: VisitDetailUiModel.VisitDefaultUiModel) {
binding.visitDefault = item
}
}

class MyVisitLogViewHolder(private val binding: ItemMyVisitLogBinding) :
VisitViewHolder(binding) {
fun bind(item: VisitDetailUiModel.VisitLogUiModel) {
binding.visitLog = item
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.woowacourse.staccato.presentation.visit.adapter

enum class VisitViewHolderType(val value: Int) {
VISIT_DEFAULT(0),
MY_VISIT_LOG(1),
;

companion object {
fun from(value: Int): VisitViewHolderType {
return when (value) {
0 -> {
VISIT_DEFAULT
}

1 -> {
MY_VISIT_LOG
}

else -> {
throw IllegalArgumentException("")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.woowacourse.staccato.presentation.visit.model

sealed class VisitDetailUiModel {
data class VisitDefaultUiModel(
val visitId: Long,
val placeName: String,
val visitImage: String,
val address: String,
val visitedAt: String,
val visitedCount: Long,
) : VisitDetailUiModel()

data class VisitLogUiModel(
val visitLogId: Long = 0,
val memberId: Long = 0,
val nickName: String,
val memberImage: String,
val content: String,
) : VisitDetailUiModel()
}
Loading

0 comments on commit cde30c6

Please sign in to comment.