Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/src/main/java/com/wafflestudio/siksha2/models/Review.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ data class Review(
@Json(name = "user_id") val userId: Long,
@Json(name = "score") val score: Double,
@Json(name = "comment") val comment: String?,
@Json(name = "keyword_reviews") val keywordReviews: List<String?>?,
@Json(name = "is_liked") val isLiked: Boolean?,
@Json(name = "name_kr") val nameKr: String?,
@Json(name = "name_en") val nameEn: String?,
@Json(name = "created_at") val createdAt: String,
@Json(name = "updated_at") val updatedAt: String,
@Json(name = "etc") val etc: Etc?
)

Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/wafflestudio/siksha2/network/SikshaApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ interface SikshaApi {
@POST("/reviews")
suspend fun leaveMenuReview(@Body req: LeaveReviewParam): NetworkResult<LeaveReviewResult>

@PATCH("/reviews")
suspend fun updateReviews(
@Query("menu_id") menuId: Long,
@Query("page") page: Long,
@Query("per_page") perPage: Long
): NetworkResult<FetchReviewsResult>

@DELETE("/reviews/{review_id}")
suspend fun deleteReviews(
@Path("review_id") reviewId: Long
): Response<Unit?>

@GET("/reviews/me")
suspend fun fetchMyReviews(
@Query("page") page: Long,
@Query("perPage") perPage: Long
): NetworkResult<FetchMyReviewsResult>

@Multipart
@POST("/reviews/images")
suspend fun leaveMenuReviewImages(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.wafflestudio.siksha2.network.dto

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.wafflestudio.siksha2.models.Review

@JsonClass(generateAdapter = true)
data class ReviewRestaurant(
@Json(name = "restaurant_id") val restaurantId: Long,
@Json(name = "name_kr") val nameKr: String?,
@Json(name = "name_en") val nameEn: String?,
@Json(name = "reviews") val reviews: List<Review>
)

@JsonClass(generateAdapter = true)
data class FetchMyReviewsResult(
@Json(name = "total_count") val totalCount: Int,
@Json(name = "has_next") val hasNext: Boolean,
@Json(name = "result") val result: List<ReviewRestaurant>
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import com.wafflestudio.siksha2.network.dto.FetchReviewDistributionResult
import com.wafflestudio.siksha2.network.dto.FetchReviewsResult
import com.wafflestudio.siksha2.network.dto.LeaveReviewParam
import com.wafflestudio.siksha2.network.dto.LeaveReviewResult
import com.wafflestudio.siksha2.network.dto.ReviewRestaurant
import com.wafflestudio.siksha2.network.result.NetworkResult
import com.wafflestudio.siksha2.ui.menuDetail.MenuMyReviewPagingSource
import com.wafflestudio.siksha2.ui.menuDetail.MenuReviewPagingSource
import com.wafflestudio.siksha2.ui.menuDetail.MenuReviewWithImagePagingSource
import com.wafflestudio.siksha2.utils.toLocalDate
Expand Down Expand Up @@ -80,6 +82,18 @@ class MenuRepository @Inject constructor(
).flow
}

fun getMyPagedReviewsByMenuIdFlow(): Flow<PagingData<ReviewRestaurant>> {
return Pager(
config = MenuMyReviewPagingSource.Config,
pagingSourceFactory = { MenuMyReviewPagingSource(sikshaApi) }
).flow
}

suspend fun deleteReview(reviewId: Long): Boolean {
val response = sikshaApi.deleteReviews(reviewId)
return response.isSuccessful
}

fun getPagedReviewsOnlyHaveImagesByMenuIdFlow(menuId: Long): Flow<PagingData<Review>> {
return Pager(
config = MenuReviewWithImagePagingSource.Config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ class SettingFragment : Fragment() {
findNavController().navigate(action)
}

binding.myReviewRow.setOnClickListener {
val action =
MainFragmentDirections.actionMainFragmentToMyReviewFragment()
findNavController().navigate(action)
}

binding.orderRestaurantRow.setOnClickListener {
val action =
MainFragmentDirections.actionMainFragmentToReorderRestaurantFragment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.wafflestudio.siksha2.ui.menuDetail

import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
Expand All @@ -30,7 +32,12 @@ import com.wafflestudio.siksha2.network.result.NetworkResult
import com.wafflestudio.siksha2.utils.hasFinalConsInKr
import com.wafflestudio.siksha2.utils.setVisibleOrGone
import com.wafflestudio.siksha2.utils.showToast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.net.HttpURLConnection
import java.net.URL

class LeaveReviewFragment : Fragment() {
private lateinit var binding: FragmentLeaveReviewBinding
Expand Down Expand Up @@ -93,6 +100,32 @@ class LeaveReviewFragment : Fragment() {
}
}

vm.editingReview.observe(viewLifecycleOwner) { review ->
review ?: return@observe

// 평점
vm.setReviewRating(review.score.toFloat())
binding.rateText.text = review.score.toInt().toString()

// 코멘트 세팅
binding.commentEdit.setText(review.comment ?: "")

// Todo : keyword 세팅, patch api 연결

// 이미지(url) → Uri 로 변환
review.etc?.images?.let { urls ->
lifecycleScope.launch {
urls.forEach { url: String ->
val file = downloadImageToFile(requireContext(), url)
file?.let {
val uri = Uri.fromFile(it)
vm.addImageUri(uri, onFailure = {})
}
}
}
}
}

vm.commentHint.observe(viewLifecycleOwner) { hint ->
binding.commentEdit.hint = hint
}
Expand Down Expand Up @@ -193,6 +226,32 @@ class LeaveReviewFragment : Fragment() {
}
}

suspend fun downloadImageToFile(
context: Context,
imageUrl: String
): File? = withContext(Dispatchers.IO) {
return@withContext try {
val url = URL(imageUrl)
val connection = url.openConnection() as HttpURLConnection
connection.connect()

if (connection.responseCode != HttpURLConnection.HTTP_OK) {
return@withContext null
}

val input = connection.inputStream
val tempFile = File.createTempFile("review_img_", ".jpg", context.cacheDir)

tempFile.outputStream().use { output ->
input.copyTo(output)
}

tempFile
} catch (e: Exception) {
null
}
}

private fun requestPermission(onGranted: () -> Unit) {
if (Build.VERSION.SDK_INT >= 33) {
onGranted()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.paging.PagingData
import com.wafflestudio.siksha2.models.Menu
import com.wafflestudio.siksha2.models.Review
import com.wafflestudio.siksha2.network.dto.LeaveReviewResult
import com.wafflestudio.siksha2.network.dto.ReviewRestaurant
import com.wafflestudio.siksha2.network.result.NetworkResult
import com.wafflestudio.siksha2.repositories.MenuRepository
import com.wafflestudio.siksha2.utils.ImageUtil
Expand Down Expand Up @@ -65,6 +66,18 @@ class MenuDetailViewModel @Inject constructor(
val reviewRating: FloatState
get() = _reviewRating

private val _deleteResult = MutableLiveData<Boolean>()
val deleteResult: LiveData<Boolean>
get() = _deleteResult

private val _editingReview = MutableLiveData<Review?>()
val editingReview: LiveData<Review?>
get() = _editingReview

fun setEditingReview(review: Review?) {
_editingReview.value = review
}

fun refreshMenu(menuId: Long) {
_networkResultState.value = State.LOADING
viewModelScope.launch {
Expand Down Expand Up @@ -103,10 +116,21 @@ class MenuDetailViewModel @Inject constructor(
}
}

fun deleteReview(id: Long) {
viewModelScope.launch {
val success = menuRepository.deleteReview(id)
_deleteResult.postValue(success)
}
}

fun getReviews(menuId: Long): Flow<PagingData<Review>> {
return menuRepository.getPagedReviewsByMenuIdFlow(menuId)
}

fun getMyReviews(): Flow<PagingData<ReviewRestaurant>> {
return menuRepository.getMyPagedReviewsByMenuIdFlow()
}

fun getReviewsWithImages(menuId: Long): Flow<PagingData<Review>> {
return menuRepository.getPagedReviewsOnlyHaveImagesByMenuIdFlow(menuId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.wafflestudio.siksha2.ui.menuDetail

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.wafflestudio.siksha2.databinding.ItemMyReviewRestaurantBinding
import com.wafflestudio.siksha2.models.Review
import com.wafflestudio.siksha2.network.dto.ReviewRestaurant

class MenuMyReviewAdapter(
private val onMenuClick: (Review) -> Unit,
private val onEditClick: (Review) -> Unit,
private val onDeleteClick: (Review) -> Unit
) :
PagingDataAdapter<ReviewRestaurant, MenuMyReviewAdapter.RestaurantViewHolder>(DiffCallback) {

companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<ReviewRestaurant>() {
override fun areItemsTheSame(oldItem: ReviewRestaurant, newItem: ReviewRestaurant): Boolean {
return oldItem.restaurantId == newItem.restaurantId
}

override fun areContentsTheSame(oldItem: ReviewRestaurant, newItem: ReviewRestaurant): Boolean {
return oldItem == newItem
}
}
}

inner class RestaurantViewHolder(private val binding: ItemMyReviewRestaurantBinding) :
RecyclerView.ViewHolder(binding.root) {

private var isExpanded = false

fun bind(item: ReviewRestaurant) {
if (bindingAdapterPosition == 0) {
isExpanded = true
binding.topDivider.isVisible = true
}

binding.restaurantName.text = item.nameKr
binding.arrowIcon.rotation = if (isExpanded) 180f else 0f

// 리뷰 어댑터 (Nested RecyclerView)
binding.reviewRecycler.layoutManager = LinearLayoutManager(binding.root.context)

val reviewAdapter = MenuMyReviewChildAdapter(onMenuClick, onEditClick, onDeleteClick)
binding.reviewRecycler.adapter = reviewAdapter

reviewAdapter.submitList(item.reviews)

// 펼치기/접기
binding.root.setOnClickListener {
isExpanded = !isExpanded
binding.arrowIcon.animate().rotation(if (isExpanded) 180f else 0f).start()
binding.reviewRecycler.isVisible = isExpanded
binding.topDivider.isVisible = isExpanded
}

// 초기에는 접혀있게
binding.reviewRecycler.isVisible = isExpanded
}
}

override fun onBindViewHolder(holder: RestaurantViewHolder, position: Int) {
getItem(position)?.let { holder.bind(it) }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RestaurantViewHolder {
val binding = ItemMyReviewRestaurantBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return RestaurantViewHolder(binding)
}
}
Loading
Loading