Skip to content

Commit b74abf5

Browse files
authored
Merge pull request #88 from LanPet-dev/develop
Develop
2 parents a9aec30 + b516329 commit b74abf5

11 files changed

Lines changed: 222 additions & 153 deletions

File tree

core/common/src/main/java/com/lanpet/core/common/Extensions.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import android.net.Uri
99
import android.provider.MediaStore
1010
import android.widget.Toast
1111
import androidx.compose.foundation.layout.WindowInsets
12+
import kotlinx.coroutines.CoroutineScope
13+
import kotlinx.coroutines.flow.Flow
14+
import kotlinx.coroutines.flow.onCompletion
15+
import kotlinx.coroutines.launch
1216
import java.io.ByteArrayOutputStream
1317
import java.io.IOException
1418
import java.text.SimpleDateFormat
@@ -157,3 +161,26 @@ fun Uri.toCompressedByteArray(
157161
e.printStackTrace()
158162
null
159163
}
164+
165+
fun <T> Flow<T>.safeScopedCall(
166+
block: suspend (T) -> Unit,
167+
scope: CoroutineScope,
168+
onFailure: suspend (Throwable) -> Unit = {},
169+
onSuccess: suspend () -> Unit = {},
170+
onComplete: suspend () -> Unit = {},
171+
) {
172+
scope.launch {
173+
runCatching {
174+
this@safeScopedCall
175+
.onCompletion {
176+
onComplete()
177+
}.collect {
178+
block(it)
179+
}
180+
}.onFailure {
181+
onFailure(it)
182+
}.onSuccess {
183+
onSuccess()
184+
}
185+
}
186+
}

core/common/src/main/java/com/lanpet/core/common/widget/NetworkImage.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package com.lanpet.core.common.widget
22

33
import androidx.annotation.DrawableRes
4-
import androidx.compose.foundation.Image
5-
import androidx.compose.foundation.layout.size
4+
import androidx.compose.foundation.layout.Box
65
import androidx.compose.runtime.Composable
76
import androidx.compose.ui.Modifier
87
import androidx.compose.ui.layout.ContentScale
98
import androidx.compose.ui.res.painterResource
10-
import androidx.compose.ui.unit.dp
119
import coil.ImageLoader
1210
import coil.compose.AsyncImage
1311
import com.lanpet.core.designsystem.R
@@ -20,20 +18,18 @@ import com.lanpet.core.manager.LocalCoilManager
2018
* @param url 로드할 이미지의 URL. null이거나 비어있을 경우 기본 이미지가 표시됩니다.
2119
* @param modifier 이미지에 적용할 Modifier. 기본값은 Modifier입니다.
2220
* @param imageLoader 이미지를 로드하는 데 사용할 ImageLoader. 기본값은 LocalCoilManager의 memoryCacheImageLoader입니다.
23-
* @param drawableRes URL이 null이거나 비어있을 때 표시할 기본 이미지 리소스 ID. 기본값은 R.drawable.img_animals입니다.
21+
* @param drawableRes 이미지 로딩이 실패했을 경우 사용될 DrawableRes ID 입니다. 기본값은 R.drawable.img_animals입니다.
2422
*/
2523
@Composable
2624
fun NetworkImage(
2725
url: String?,
2826
modifier: Modifier = Modifier,
27+
@DrawableRes drawableRes: Int? = R.drawable.img_animals,
2928
imageLoader: ImageLoader = LocalCoilManager.current.memoryCacheImageLoader,
30-
@DrawableRes drawableRes: Int = R.drawable.img_animals,
3129
) {
3230
if (url.isNullOrEmpty()) {
33-
Image(
34-
painter = painterResource(id = drawableRes),
35-
contentDescription = "default_image",
36-
modifier = modifier.size(66.dp),
31+
Box(
32+
modifier = modifier,
3733
)
3834
} else {
3935
AsyncImage(

data/repository/src/main/java/com/lanpet/data/repository/FreeBoardRepositoryImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class FreeBoardRepositoryImpl
150150
emit(true)
151151
} catch (e: Exception) {
152152
Timber.e(e)
153-
emit(false)
153+
throw e
154154
}
155155
}.flowOn(Dispatchers.IO)
156156

@@ -164,7 +164,7 @@ class FreeBoardRepositoryImpl
164164
emit(true)
165165
} catch (e: Exception) {
166166
Timber.e(e)
167-
emit(false)
167+
throw e
168168
}
169169
}.flowOn(Dispatchers.IO)
170170

feature/free/src/main/java/com/lanpet/free/screen/FreeBoardCommentDetailScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ fun FreeBoardCommentDetailScreen(
138138
freeBoardComment = singleCommentUiState.comment,
139139
profileNickname = profileNickname,
140140
onMoreSubCommentClick = onMoreSubComment,
141+
hasMoreSubComment = singleCommentUiState.hasMoreSubComment,
141142
)
142143
}
143144
CommentInput(

feature/free/src/main/java/com/lanpet/free/screen/FreeBoardDetailScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ fun FreeBoardCommentSection(
592592
)
593593
},
594594
profileNickname = nickname,
595+
hasMoreSubComment = comment.subComments.size > 9,
595596
)
596597
}
597598
if (canLoadMore) {

feature/free/src/main/java/com/lanpet/free/viewmodel/FreeBoardCommentDetailViewModel.kt

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import androidx.lifecycle.SavedStateHandle
44
import androidx.lifecycle.ViewModel
55
import androidx.lifecycle.viewModelScope
66
import com.lanpet.core.auth.AuthManager
7+
import com.lanpet.core.common.safeScopedCall
78
import com.lanpet.domain.model.free.FreeBoardComment
9+
import com.lanpet.domain.model.free.FreeBoardSubComment
810
import com.lanpet.domain.model.free.FreeBoardWriteComment
911
import com.lanpet.domain.usecase.freeboard.GetFreeBoardSubCommentListUseCase
1012
import com.lanpet.domain.usecase.freeboard.WriteSubCommentUseCase
@@ -13,9 +15,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
1315
import kotlinx.coroutines.flow.MutableStateFlow
1416
import kotlinx.coroutines.flow.asSharedFlow
1517
import kotlinx.coroutines.flow.asStateFlow
16-
import kotlinx.coroutines.flow.first
1718
import kotlinx.coroutines.flow.update
18-
import kotlinx.coroutines.launch
1919
import kotlinx.serialization.json.Json
2020
import javax.inject.Inject
2121

@@ -55,65 +55,96 @@ class FreeBoardCommentDetailViewModel
5555
val comment = commentInput.value
5656
if (comment.isEmpty()) return
5757

58-
viewModelScope.launch {
59-
runCatching {
60-
writeSubCommentUseCase(
61-
postId = postId,
62-
commentId = freeBoardComment.id,
63-
writeComment =
64-
FreeBoardWriteComment(
65-
profileId = authManager.defaultUserProfile.value.id,
66-
comment = commentInput.value,
67-
),
68-
).first()
69-
58+
writeSubCommentUseCase(
59+
postId = postId,
60+
commentId = freeBoardComment.id,
61+
writeComment =
62+
FreeBoardWriteComment(
63+
profileId = authManager.defaultUserProfile.value.id,
64+
comment = comment,
65+
),
66+
).safeScopedCall(
67+
scope = viewModelScope,
68+
block = {
7069
commentInput.value = ""
7170
_event.emit(CommentDetailEvent.WriteSubCommentSuccess())
72-
}.onFailure {
71+
refreshSubComment()
72+
},
73+
onFailure = {
7374
_event.emit(CommentDetailEvent.WriteSubCommentFail())
74-
}
75+
},
76+
)
77+
}
78+
79+
private fun refreshSubComment() {
80+
_singleCommentUiState.update {
81+
SingleCommentUiState.Loading
7582
}
83+
subCommentPagingState = CursorPagingState()
84+
getSubComment()
7685
}
7786

7887
fun getSubComment() {
7988
if (!subCommentPagingState.hasNext) return
8089

81-
viewModelScope.launch {
82-
runCatching {
83-
getFreeBoardSubCommentListUseCase(
84-
postId = postId,
85-
commentId = freeBoardComment.id,
86-
cursor = subCommentPagingState.cursor,
87-
size = subCommentPagingState.size,
88-
direction = subCommentPagingState.direction,
89-
).collect {
90-
_singleCommentUiState.update { state ->
91-
when (state) {
92-
is SingleCommentUiState.Success -> {
93-
SingleCommentUiState.Success(
94-
comment =
95-
state.comment.copy(
96-
subComments =
97-
state.comment.subComments + it.data,
98-
),
99-
)
100-
}
101-
102-
else -> {
103-
SingleCommentUiState.Success(
104-
comment = freeBoardComment.copy(subComments = it.data),
105-
)
106-
}
90+
getFreeBoardSubCommentListUseCase(
91+
postId = postId,
92+
commentId = freeBoardComment.id,
93+
cursor = subCommentPagingState.cursor,
94+
size = subCommentPagingState.size,
95+
direction = subCommentPagingState.direction,
96+
).safeScopedCall(
97+
scope = viewModelScope,
98+
block = { subCommentList ->
99+
_singleCommentUiState.update { state ->
100+
when (state) {
101+
is SingleCommentUiState.Success -> {
102+
SingleCommentUiState.Success(
103+
comment =
104+
state.comment.copy(
105+
subComments =
106+
state.comment.subComments + subCommentList.data,
107+
),
108+
hasMoreSubComment = subCommentList.paginationInfo.hasNext,
109+
)
110+
}
111+
112+
else -> {
113+
SingleCommentUiState.Success(
114+
comment = freeBoardComment.copy(subComments = subCommentList.data),
115+
hasMoreSubComment = subCommentList.paginationInfo.hasNext,
116+
)
107117
}
108118
}
119+
}
109120

110-
subCommentPagingState =
111-
subCommentPagingState.copy(
112-
cursor = it.paginationInfo.nextCursor,
113-
hasNext = it.paginationInfo.hasNext,
114-
)
121+
subCommentPagingState =
122+
subCommentPagingState.copy(
123+
cursor = subCommentList.paginationInfo.nextCursor,
124+
hasNext = subCommentList.paginationInfo.hasNext,
125+
)
126+
},
127+
)
128+
}
129+
130+
private fun updateSubCommentCache(cache: FreeBoardSubComment) {
131+
if (subCommentPagingState.hasNext) return
132+
133+
_singleCommentUiState.update {
134+
when (it) {
135+
is SingleCommentUiState.Success -> {
136+
SingleCommentUiState.Success(
137+
comment =
138+
it.comment.copy(
139+
subComments = it.comment.subComments + cache,
140+
),
141+
)
115142
}
116-
}.onFailure { e ->
143+
144+
else ->
145+
SingleCommentUiState.Success(
146+
comment = freeBoardComment.copy(subComments = listOf(cache)),
147+
)
117148
}
118149
}
119150
}
@@ -126,6 +157,7 @@ class FreeBoardCommentDetailViewModel
126157
sealed interface SingleCommentUiState {
127158
data class Success(
128159
val comment: FreeBoardComment,
160+
val hasMoreSubComment: Boolean = false,
129161
) : SingleCommentUiState
130162

131163
data class Error(

0 commit comments

Comments
 (0)