From 0b9127fc74e621f854447d22edf068343ce7d0cc Mon Sep 17 00:00:00 2001 From: soopeach Date: Tue, 12 Mar 2024 00:19:58 +0900 Subject: [PATCH] =?UTF-8?q?[feat/register=5Freview]:=20=ED=9B=84=EA=B8=B0?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/PhotoWillBeUploadedAdapter.kt | 64 ++++ .../RestaurantDetailFragment.kt | 75 ++++ .../viewmodel/RestaurantDetailViewModel.kt | 28 +- .../src/main/res/drawable/add_image_icon.png | Bin 0 -> 1082 bytes .../res/drawable/bg_rounded_6_main500.xml | 9 + .../src/main/res/drawable/delete_icon.png | Bin 0 -> 1032 bytes .../res/layout/fragment_restaurant_detail.xml | 319 ++++++++++-------- .../layout/item_photo_will_be_uploaded.xml | 25 ++ .../main/res/navigation/main_nav_graph.xml | 3 + 9 files changed, 384 insertions(+), 139 deletions(-) create mode 100644 presentation/src/main/java/org/gdsc/presentation/view/mypage/adapter/PhotoWillBeUploadedAdapter.kt create mode 100644 presentation/src/main/res/drawable/add_image_icon.png create mode 100644 presentation/src/main/res/drawable/bg_rounded_6_main500.xml create mode 100644 presentation/src/main/res/drawable/delete_icon.png create mode 100644 presentation/src/main/res/layout/item_photo_will_be_uploaded.xml diff --git a/presentation/src/main/java/org/gdsc/presentation/view/mypage/adapter/PhotoWillBeUploadedAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/mypage/adapter/PhotoWillBeUploadedAdapter.kt new file mode 100644 index 00000000..62b1d820 --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/mypage/adapter/PhotoWillBeUploadedAdapter.kt @@ -0,0 +1,64 @@ +package org.gdsc.presentation.view.mypage.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.gdsc.presentation.databinding.ItemPhotoWillBeUploadedBinding + +class PhotoWillBeUploadedAdapter( + private val onDeleteButtonClicked: (String) -> Unit +) : + ListAdapter( + diffUtil + ) { + inner class PhotoWillBeUploadedViewHolder(private val binding: ItemPhotoWillBeUploadedBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(url: String) { + + Glide.with(binding.root) + .load(url.toUri()) + .into(binding.photoWillBeUploaded) + + binding.deleteButton.setOnClickListener { + onDeleteButtonClicked(url) + } + } + } + + companion object { + val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): PhotoWillBeUploadedViewHolder { + val inflater = LayoutInflater.from(parent.context) + return PhotoWillBeUploadedViewHolder( + ItemPhotoWillBeUploadedBinding.inflate( + inflater, + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: PhotoWillBeUploadedViewHolder, position: Int) { + holder.apply { + bind(getItem(position)) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/mypage/restaurantdetail/RestaurantDetailFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/mypage/restaurantdetail/RestaurantDetailFragment.kt index 724f0164..a9750e84 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/mypage/restaurantdetail/RestaurantDetailFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/mypage/restaurantdetail/RestaurantDetailFragment.kt @@ -8,15 +8,26 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast +import androidx.core.net.toUri import androidx.fragment.app.activityViewModels +import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody import org.gdsc.presentation.R import org.gdsc.presentation.databinding.FragmentRestaurantDetailBinding +import org.gdsc.presentation.utils.BitmapUtils.getCompressedBitmapFromUri +import org.gdsc.presentation.utils.BitmapUtils.saveBitmapToFile import org.gdsc.presentation.utils.CalculatorUtils +import org.gdsc.presentation.utils.repeatWhenUiStarted +import org.gdsc.presentation.view.mypage.adapter.PhotoWillBeUploadedAdapter import org.gdsc.presentation.view.mypage.adapter.RestaurantDetailPagerAdapter import org.gdsc.presentation.view.mypage.viewmodel.RestaurantDetailViewModel @@ -28,6 +39,10 @@ class RestaurantDetailFragment : Fragment() { private val viewModel: RestaurantDetailViewModel by activityViewModels() + private val adapter = PhotoWillBeUploadedAdapter { + viewModel.deletePhotoForReviewState(it) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -41,6 +56,66 @@ class RestaurantDetailFragment : Fragment() { setButtons() setTabLayout() observeData() + + repeatWhenUiStarted { + viewModel.photosForReviewState.collect { + adapter.submitList(it) + } + } + + binding.rvImageListWillBeUploaded.adapter = adapter + + binding.addImageIcon.setOnClickListener { + val directions = + RestaurantDetailFragmentDirections.actionRestaurantDetailFragmentToMultiImagePickerFragment() + + findNavController().navigate(directions) + } + + binding.btnRegister.setOnClickListener { + + val pictures = mutableListOf() + + viewModel.photosForReviewState.value.forEachIndexed { index, sUri -> + + sUri.toUri() + .getCompressedBitmapFromUri(requireContext()) + ?.saveBitmapToFile(requireContext(), "$index.jpg")?.let { imageFile -> + + val requestFile = + RequestBody.create( + MediaType.parse("image/png"), + imageFile + ) + + val body = + MultipartBody.Part.createFormData( + "reviewImages", + imageFile.name, + requestFile + ) + + pictures.add(body) + + } + + } + + viewModel.postReview( + binding.etReview.text.toString(), + pictures + ) { + binding.etReview.text.clear() + Toast.makeText(requireContext(), "후기가 등록되었습니다!", Toast.LENGTH_SHORT).show() + } + } + + setFragmentResultListener("pickImages") { _, bundle -> + val images = bundle.getStringArray("imagesUri") + viewModel.setPhotosForReviewState(images?.toList() ?: emptyList()) + + if (images.isNullOrEmpty()) return@setFragmentResultListener + } } private fun setButtons() { diff --git a/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/RestaurantDetailViewModel.kt b/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/RestaurantDetailViewModel.kt index 6cee9ab6..8e9639b8 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/RestaurantDetailViewModel.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/mypage/viewmodel/RestaurantDetailViewModel.kt @@ -5,12 +5,15 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import okhttp3.MultipartBody import org.gdsc.domain.model.Review import org.gdsc.domain.model.UserInfo import org.gdsc.domain.model.response.RestaurantInfoResponse import org.gdsc.domain.usecase.GetRestaurantInfoUseCase import org.gdsc.domain.usecase.GetRestaurantReviewsUseCase +import org.gdsc.domain.usecase.PostReviewUseCase import org.gdsc.domain.usecase.user.GetOtherUserInfoUseCase import org.gdsc.presentation.JmtLocationManager import javax.inject.Inject @@ -21,7 +24,8 @@ class RestaurantDetailViewModel private val jmtLocationManager: JmtLocationManager, private val getRestaurantInfoUseCase: GetRestaurantInfoUseCase, private val getOtherUserInfoUseCase: GetOtherUserInfoUseCase, - private val getRestaurantReviewsUseCase: GetRestaurantReviewsUseCase + private val getRestaurantReviewsUseCase: GetRestaurantReviewsUseCase, + private val postReviewUseCase: PostReviewUseCase ): ViewModel() { private var _restaurantInfo: MutableStateFlow = MutableStateFlow(null) @@ -36,6 +40,17 @@ class RestaurantDetailViewModel val reviews: StateFlow> get() = _reviews + private var _photosForReviewState: MutableStateFlow> = + MutableStateFlow(emptyList()) + val photosForReviewState = _photosForReviewState.asStateFlow() + + fun setPhotosForReviewState(images: List) { + _photosForReviewState.value = images + } + + fun deletePhotoForReviewState(image: String) { + _photosForReviewState.value = _photosForReviewState.value - image + } init { viewModelScope.launch { @@ -54,4 +69,15 @@ class RestaurantDetailViewModel } + fun postReview(content: String, pictures: List, onSuccess: () -> Unit) { + viewModelScope.launch { + val isSuccess = postReviewUseCase(1, content, pictures) + + if (isSuccess) { + _photosForReviewState.value = emptyList() + onSuccess() + } + } + } + } \ No newline at end of file diff --git a/presentation/src/main/res/drawable/add_image_icon.png b/presentation/src/main/res/drawable/add_image_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0fd53b1880948bdf14831d970e985c1eb66da749 GIT binary patch literal 1082 zcmV-A1jYM_P)19cT9M%P)} z$N^=*nk@H#M*O?QCH#cbFXX%qhgq9gR0Sm@9*=k7uoyvMU1Ist=dbV{4G`6?{m{{J zJXMF+2e7w-jPriC+Z`!c55#48gv8)sm$vTD-?h}SB(Lk;PR3Wz$WTE&aAR_MgmXTG zhjmZ@2M^R>qjN9;U)}~rCZ>JLw)lcMb)t1wSyX6sS>8xH7VGNJyhz>#IPg9wk|s|> z+WDIgi?9%0*1dKi{Auf>(sJx5ob5iDIxoP$&ak;PG*D0&zIypQPZznI^Sxo5;6)iQ zi3kW`lZF&U9u;YQJvEG67=@~x&Qx7TYjfIzN@74!lxGfyV^BTsL<60|yr4Be4Wk_` z&AM|&C8L32qm2<8ZnxJd%NJPj&y5%wC??u?eRsPQjMWOT(Ae;RUt3>~sX#0i>taCYa*PrhPG<>rG_B>znc5kPOhU9G`kLcmL`9thOhJZ>vAR!FfAn zpV~Upvr!b=hoXBKO-^hvItK&sn@kb2QP1knUxVX!9{4f3wA1eEW#A*+d-4h%K7S2* z;t5L@vSf?|6P6pWQaoX)0jtFmmKm@^JYk6eyTlWw8?aM6VX6VU#S^9(D1~^! z6a%FaPvo5o;T9~az8g|^XPfwEF^2nuyn6sQ`jcQY+C7Fy-Ue{f;>8C}vJ95dD&Ee& z2%bK3uHM0beqpNy3rR+sag1zcXYod5s;07*qoM6N<$g7Hc7 AVgLXD literal 0 HcmV?d00001 diff --git a/presentation/src/main/res/drawable/bg_rounded_6_main500.xml b/presentation/src/main/res/drawable/bg_rounded_6_main500.xml new file mode 100644 index 00000000..8b55e5d7 --- /dev/null +++ b/presentation/src/main/res/drawable/bg_rounded_6_main500.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/delete_icon.png b/presentation/src/main/res/drawable/delete_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..179c23a21160af69efdbd0e1645c2b60f2a52fc8 GIT binary patch literal 1032 zcmV+j1o!)iP)fS;K9TLXz*mD z9FRg0780O(FhwsUB2>U1JfWg4NOyd%+6CI}c2U4Dne6V&+xfoP_ukGMfd33;)=+Y} ztjern=fH}9g9MygvD@}%IEXW0V*!Cf-_X+zkJ)(>3LxU|Uz7?otz-};H3JePvyI^P zd%Zy;L(ypP@QE`OKj#)asM3ke0_H(N?GOUjuFM?vZhX`q&rtUZ(5ioYRqF9mm%wU( zLDS|GOO@>L%)}1qlF#*`kL|FU!1&mlvgvrV2qD@ISKhknD?xFy1l)YOD_ zAyw75!UAaZD8UtX((S46xTP6^4A%TJ- zx`=kkW!=CQ8jNf|OiyF`1jllZXnltc><@?VA^ZLq^4eQV&Hnbt~_AVcofv5@x1(h?i8w|8}`k?l99jpd9VL{|JLExr$I88+Vz+g;& z|G?0S$O*(PsEp05YC2Tuo%GM@~NP9~{!n4l^SBy&V_DkHHZoZFjZ=i{(`eDEg}|U5FtkpTB-H zrkkRax-=FSSTSLt(){~R$XOd0U`ukcE_noc*+F}P5g3PNx`wyoZ5jJUL>4NKS9VQI zPJ%6c7bGPm!3`wx$$c?4v|4HzX=(6wViN8>Xo1<;Z}A1FP&E}Comt$D^PM#upc>Wz zNZRD@@s`rPwjC>~r2$Ls6{X}~A_ywDBM+K@fQ+>ogHl*y;OgCed9hSqR+3>Y{%nL~2gn84~)gL2IX41YFwIx1c2^A!ougi!q!f)dW4IFT)_SYSMWGrRrWUOADy z>C-Vm!sA|7S}4_Fo~0a^B#PUm0zer9s5doBb30000 - + app:layout_constraintBottom_toTopOf="@id/divider" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - + app:layout_constraintBottom_toBottomOf="parent" + > - - - - - - - - - + + + + + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/ct_distance_and_category" + android:layout_marginTop="4dp"> + + + + + + + + + + + + + + - + - + - - - - - - - - - + - + app:layout_constraintTop_toTopOf="parent" + style="@style/text_medium_medium" + android:textColor="@color/grey900"/> - + app:layout_constraintTop_toBottomOf="@id/et_review" + android:layout_marginTop="27dp"/> - + app:layout_constraintBottom_toBottomOf="@id/add_image_icon" + style="@style/text_medium_medium" + android:textColor="@color/white"/> + + diff --git a/presentation/src/main/res/layout/item_photo_will_be_uploaded.xml b/presentation/src/main/res/layout/item_photo_will_be_uploaded.xml new file mode 100644 index 00000000..3bd94ed5 --- /dev/null +++ b/presentation/src/main/res/layout/item_photo_will_be_uploaded.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/navigation/main_nav_graph.xml b/presentation/src/main/res/navigation/main_nav_graph.xml index 83c8e114..37c1baa9 100644 --- a/presentation/src/main/res/navigation/main_nav_graph.xml +++ b/presentation/src/main/res/navigation/main_nav_graph.xml @@ -173,6 +173,9 @@ +