Skip to content

Commit

Permalink
Merge branch 'develop' into feat/all_search
Browse files Browse the repository at this point in the history
  • Loading branch information
soopeach authored Mar 12, 2024
2 parents 1d6d62d + 35ed715 commit e7a57aa
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.gdsc.data.datasource

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import okhttp3.MultipartBody
import org.gdsc.data.database.RegisteredRestaurant
import org.gdsc.data.database.ReviewPaging
import org.gdsc.data.model.RegisteredRestaurantResponse
Expand Down Expand Up @@ -47,4 +48,6 @@ interface RestaurantDataSource {

suspend fun getRestaurantReviews(restaurantId: Int): ReviewPaging

suspend fun postRestaurantReview(restaurantId: Int, reviewContent: String, reviewImages: List<MultipartBody.Part>): Boolean

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.gdsc.data.database.RegisteredRestaurant
import org.gdsc.data.database.RestaurantByMapPagingSource
Expand Down Expand Up @@ -271,4 +273,16 @@ class RestaurantDataSourceImpl @Inject constructor(
return restaurantAPI.getRestaurantReviews(restaurantId).data
}

override suspend fun postRestaurantReview(
restaurantId: Int,
reviewContent: String,
reviewImages: List<MultipartBody.Part>
): Boolean {

return restaurantAPI.postRestaurantReview(
restaurantId,
MultipartBody.Part.createFormData("reviewContent", reviewContent), reviewImages
).code == "RESTAURANT_REVIEW_CREATED"
}

}
8 changes: 8 additions & 0 deletions data/src/main/java/org/gdsc/data/network/RestaurantAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,12 @@ interface RestaurantAPI {
@Path("recommendRestaurantId") recommendRestaurantId: Int,
): Response<ReviewPaging>

@Multipart
@POST("/api/v1/restaurant/{recommendRestaurantId}/review")
suspend fun postRestaurantReview(
@Path("recommendRestaurantId") recommendRestaurantId: Int,
@Part reviewContent: MultipartBody.Part,
@Part reviewImages: List<MultipartBody.Part>,
): Response<String>

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.paging.PagingData
import androidx.paging.map
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import okhttp3.MultipartBody
import org.gdsc.data.datasource.RestaurantDataSource
import org.gdsc.domain.DrinkPossibility
import org.gdsc.domain.FoodCategory
Expand Down Expand Up @@ -197,4 +198,13 @@ class RestaurantRepositoryImpl @Inject constructor(
}
}

}

override suspend fun postRestaurantReview(
restaurantId: Int,
reviewContent: String,
reviewImages: List<MultipartBody.Part>
): Boolean {
return restaurantDataSource.postRestaurantReview(restaurantId, reviewContent, reviewImages)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.gdsc.domain.repository

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import okhttp3.MultipartBody
import org.gdsc.domain.DrinkPossibility
import org.gdsc.domain.FoodCategory
import org.gdsc.domain.SortType
Expand Down Expand Up @@ -45,5 +46,7 @@ interface RestaurantRepository {
suspend fun getRestaurantReviews(restaurantId: Int): List<Review>

suspend fun getRegisteredRestaurantsBySearchWithLimitCount(keyword: String?, userLocation: Location?, limit: Int): List<RegisteredRestaurant>

suspend fun postRestaurantReview(restaurantId: Int, reviewContent: String, reviewImages: List<MultipartBody.Part>): Boolean

}
14 changes: 14 additions & 0 deletions domain/src/main/java/org/gdsc/domain/usecase/PostReviewUseCase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.gdsc.domain.usecase

import okhttp3.MultipartBody
import org.gdsc.domain.repository.RestaurantRepository
import javax.inject.Inject

class PostReviewUseCase @Inject constructor(
private val restaurantRepository: RestaurantRepository
) {

suspend operator fun invoke(restaurantId: Int, reviewContent: String, reviewImages: List<MultipartBody.Part>): Boolean {
return restaurantRepository.postRestaurantReview(restaurantId, reviewContent, reviewImages)
}
}
Original file line number Diff line number Diff line change
@@ -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<String, PhotoWillBeUploadedAdapter.PhotoWillBeUploadedViewHolder>(
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<String>() {
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))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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?
Expand All @@ -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<MultipartBody.Part>()

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<RestaurantInfoResponse?> = MutableStateFlow(null)
Expand All @@ -36,6 +40,17 @@ class RestaurantDetailViewModel
val reviews: StateFlow<List<Review>>
get() = _reviews

private var _photosForReviewState: MutableStateFlow<List<String>> =
MutableStateFlow(emptyList())
val photosForReviewState = _photosForReviewState.asStateFlow()

fun setPhotosForReviewState(images: List<String>) {
_photosForReviewState.value = images
}

fun deletePhotoForReviewState(image: String) {
_photosForReviewState.value = _photosForReviewState.value - image
}

init {
viewModelScope.launch {
Expand All @@ -54,4 +69,15 @@ class RestaurantDetailViewModel

}

fun postReview(content: String, pictures: List<MultipartBody.Part>, onSuccess: () -> Unit) {
viewModelScope.launch {
val isSuccess = postReviewUseCase(1, content, pictures)

if (isSuccess) {
_photosForReviewState.value = emptyList()
onSuccess()
}
}
}

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions presentation/src/main/res/drawable/bg_rounded_6_main500.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector >
<item xmlns:android="http://schemas.android.com/apk/res/android">
<shape android:shape="rectangle">
<solid android:color="@color/main500" />
<corners android:radius="6dp" />
</shape>
</item>
</selector>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e7a57aa

Please sign in to comment.