Skip to content

Commit dc17392

Browse files
authored
Merge pull request #800 from Team-WSS/feat/799
feat: 작품 피드 장르별 색상 반영
2 parents 4230792 + 37f5ee3 commit dc17392

9 files changed

Lines changed: 169 additions & 21 deletions

File tree

app/src/main/java/com/into/websoso/core/common/ui/custom/WebsosoCustomSnackBar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.into.websoso.core.common.ui.custom
22

33
import android.R.color.transparent
4+
import android.annotation.SuppressLint
45
import android.view.LayoutInflater
56
import android.view.View
67
import androidx.core.content.ContextCompat
78
import com.google.android.material.snackbar.Snackbar
89
import com.into.websoso.databinding.CustomSnackBarBinding
910

11+
@SuppressLint("RestrictedApi")
1012
class WebsosoCustomSnackBar(
1113
view: View,
1214
) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.into.websoso.ui
2+
3+
import android.graphics.drawable.GradientDrawable
4+
import android.view.View
5+
import android.widget.ImageView
6+
import androidx.annotation.ColorRes
7+
import androidx.core.content.ContextCompat
8+
import androidx.databinding.BindingAdapter
9+
import com.into.websoso.R
10+
11+
@BindingAdapter("imageTint")
12+
fun ImageView.setImageTint(
13+
@ColorRes colorRes: Int?,
14+
) {
15+
if (colorRes == null) {
16+
clearColorFilter()
17+
} else {
18+
setColorFilter(ContextCompat.getColor(context, colorRes))
19+
}
20+
}
21+
22+
@BindingAdapter(value = ["dynamicBackgroundColor", "genreName"], requireAll = false)
23+
fun View.setGenreBackground(
24+
@ColorRes colorRes: Int?,
25+
genreName: String?,
26+
) {
27+
val drawable = background?.mutate() as? GradientDrawable ?: return
28+
29+
if (colorRes != null) {
30+
drawable.setColor(ContextCompat.getColor(context, colorRes))
31+
}
32+
33+
val normalized = genreName?.trim()
34+
val isEtc = normalized.isNullOrEmpty() || normalized == "기타"
35+
36+
if (isEtc) {
37+
val strokeWidth = (1 * context.resources.displayMetrics.density).toInt()
38+
val strokeColor = ContextCompat.getColor(context, R.color.gray_70_DFDFE3)
39+
drawable.setStroke(strokeWidth, strokeColor)
40+
} else {
41+
drawable.setStroke(0, 0)
42+
}
43+
}

app/src/main/java/com/into/websoso/ui/main/feed/adapter/FeedViewHolder.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class FeedViewHolder(
2020
binding.feed = feed.copy(
2121
user = feed.user.copy(avatarImage = itemView.getS3ImageUrl(feed.user.avatarImage)),
2222
)
23-
2423
binding.clFeedLike.isSelected = feed.isLiked
2524
}
2625

app/src/main/java/com/into/websoso/ui/main/feed/model/FeedModel.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.into.websoso.ui.main.feed.model
22

3+
import com.into.websoso.ui.novelDetail.model.Category
4+
35
data class FeedModel(
46
val user: UserModel,
57
val createdDate: String,
@@ -33,7 +35,27 @@ data class FeedModel(
3335
val title: String?,
3436
val rating: Float?,
3537
val ratingCount: Int?,
38+
val genre: String = "",
3639
) {
3740
val isNothing: Boolean = id == null
41+
42+
private val normalizedGenreName: String
43+
get() = genre.trim().ifEmpty { ETC }
44+
45+
private val category: Category
46+
get() = Category.from(normalizedGenreName)
47+
48+
val novelBackgroundColor: Int
49+
get() = category.backgroundColor
50+
51+
val novelIconColor: Int
52+
get() = category.iconColor
53+
54+
val isEtcGenre: Boolean
55+
get() = normalizedGenreName == ETC
56+
57+
companion object {
58+
private const val ETC = "기타"
59+
}
3860
}
3961
}
Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
package com.into.websoso.ui.novelDetail.model
22

3+
import androidx.annotation.ColorRes
4+
import com.into.websoso.R
5+
36
enum class Category(
47
val titleKr: String,
58
val titleEn: String,
9+
@ColorRes val iconColor: Int,
10+
@ColorRes val backgroundColor: Int,
611
) {
7-
ROMANCE("로맨스", "romance"),
8-
ROMANCE_FANTASY("로판", "romanceFantasy"),
9-
BOYS_LOVE("BL", "bl"),
10-
FANTASY("판타지", "fantasy"),
11-
MODERN_FANTASY("현판", "modernFantasy"),
12-
WUXIA("무협", "wuxia"),
13-
DRAMA("드라마", "drama"),
14-
MYSTERY("미스터리", "mystery"),
15-
LIGHT_NOVEL("라노벨", "lightNovel"),
12+
ROMANCE("로맨스", "romance", R.color.romance_FF3378, R.color.romance_FFE5ED),
13+
ROMANCE_FANTASY(
14+
"로판",
15+
"romanceFantasy",
16+
R.color.romance_fantasy_873EFF,
17+
R.color.romance_fantasy_F1E8FF,
18+
),
19+
BOYS_LOVE("BL", "bl", R.color.bl_02A8A4, R.color.bl_E0F6F5),
20+
FANTASY("판타지", "fantasy", R.color.fantasy_4D63FF, R.color.fantasy_E9ECFF),
21+
MODERN_FANTASY(
22+
"현판",
23+
"modernFantasy",
24+
R.color.modern_fantasy_525CDE,
25+
R.color.modern_fantasy_EAEBFF,
26+
),
27+
WUXIA("무협", "wuxia", R.color.wuxia_E05727, R.color.wuxia_FFF1EB),
28+
DRAMA("드라마", "drama", R.color.drama_02A8A4, R.color.drama_E0F6F5),
29+
MYSTERY("미스터리", "mystery", R.color.mystery_AD55EC, R.color.mystery_F6EEFC),
30+
LIGHT_NOVEL("라노벨", "lightNovel", R.color.light_novel_34C2EB, R.color.light_novel_E6F8FD),
31+
ETC("기타", "etc", R.color.etc_6457FC, R.color.etc_FFFFFF),
1632
;
1733

1834
companion object {
1935
fun from(title: String): Category =
20-
Category.entries.find { category ->
36+
entries.find { category ->
2137
title == category.titleEn || title == category.titleKr
22-
} ?: throw IllegalArgumentException()
38+
} ?: throw IllegalArgumentException("존재하지 않는 장르입니다: $title")
2339
}
2440
}

app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedFragment.kt

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import com.into.websoso.ui.main.feed.dialog.FeedReportDialogFragment
4848
import com.into.websoso.ui.main.feed.dialog.RemoveMenuType.REMOVE_FEED
4949
import com.into.websoso.ui.main.feed.dialog.ReportMenuType
5050
import com.into.websoso.ui.novelDetail.NovelDetailActivity
51+
import com.into.websoso.ui.novelDetail.NovelDetailViewModel
5152
import com.into.websoso.ui.novelFeed.model.NovelFeedUiState
5253
import com.into.websoso.ui.otherUserPage.BlockUserDialogFragment
5354
import com.into.websoso.ui.otherUserPage.OtherUserPageActivity
@@ -60,6 +61,7 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
6061
private val popupBinding: MenuFeedPopupBinding
6162
get() = _popupBinding ?: error("error: binding is null")
6263
private val novelFeedViewModel: NovelFeedViewModel by activityViewModels()
64+
private val novelDetailViewModel: NovelDetailViewModel by activityViewModels()
6365
private val feedAdapter: FeedAdapter by lazy { FeedAdapter(onClickFeedItem()) }
6466
private val singleEventHandler: SingleEventHandler by lazy { SingleEventHandler.from() }
6567
private lateinit var activityResultCallback: ActivityResultLauncher<Intent>
@@ -70,7 +72,6 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
7072
savedInstanceState: Bundle?,
7173
): View {
7274
_popupBinding = MenuFeedPopupBinding.inflate(inflater, container, false)
73-
7475
return super.onCreateView(inflater, container, savedInstanceState)
7576
}
7677

@@ -221,19 +222,21 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
221222
noinline event: () -> Unit,
222223
) {
223224
when (Dialog::class) {
224-
DialogRemovePopupMenuBinding::class ->
225+
DialogRemovePopupMenuBinding::class -> {
225226
FeedRemoveDialogFragment
226227
.newInstance(
227228
menuType = menuType,
228229
event = { event() },
229230
).show(childFragmentManager, FeedRemoveDialogFragment.TAG)
231+
}
230232

231-
DialogReportPopupMenuBinding::class ->
233+
DialogReportPopupMenuBinding::class -> {
232234
FeedReportDialogFragment
233235
.newInstance(
234236
menuType = menuType,
235237
event = { event() },
236238
).show(childFragmentManager, FeedReportDialogFragment.TAG)
239+
}
237240
}
238241
}
239242

@@ -281,9 +284,11 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
281284
activityResultCallback =
282285
registerForActivityResult(StartActivityForResult()) { result ->
283286
when (result.resultCode) {
284-
OtherUserProfileBack.RESULT_OK -> novelFeedViewModel.updateRefreshedFeeds(
285-
novelId,
286-
)
287+
OtherUserProfileBack.RESULT_OK -> {
288+
novelFeedViewModel.updateRefreshedFeeds(
289+
novelId,
290+
)
291+
}
287292

288293
BlockUser.RESULT_OK -> {
289294
val nickname =
@@ -361,8 +366,14 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
361366
private fun setupObserver() {
362367
novelFeedViewModel.feedUiState.observe(viewLifecycleOwner) { novelFeedUiState ->
363368
when {
364-
novelFeedUiState.loading -> binding.wllNovelFeed.setWebsosoLoadingVisibility(true)
365-
novelFeedUiState.error -> binding.wllNovelFeed.setLoadingLayoutVisibility(false)
369+
novelFeedUiState.loading -> {
370+
binding.wllNovelFeed.setWebsosoLoadingVisibility(true)
371+
}
372+
373+
novelFeedUiState.error -> {
374+
binding.wllNovelFeed.setLoadingLayoutVisibility(false)
375+
}
376+
366377
!novelFeedUiState.loading -> {
367378
binding.wllNovelFeed.setWebsosoLoadingVisibility(false)
368379
binding.sptrNovelFeedRefresh.setRefreshing(false)
@@ -376,11 +387,35 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
376387
binding.rvNovelFeed.scrollToPosition(0)
377388
}
378389
}
390+
391+
novelDetailViewModel.novelDetailModel.observe(viewLifecycleOwner) {
392+
val currentFeedState = novelFeedViewModel.feedUiState.value ?: return@observe
393+
if (currentFeedState.feeds.isEmpty()) return@observe
394+
updateFeeds(currentFeedState)
395+
}
379396
}
380397

381398
private fun updateFeeds(novelFeedUiState: NovelFeedUiState) {
382399
binding.clNovelFeedNone.isVisible = novelFeedUiState.feeds.isEmpty()
383-
val feeds = novelFeedUiState.feeds.map { Feed(it) }
400+
401+
val genre: String =
402+
novelDetailViewModel.novelDetailModel.value
403+
?.novel
404+
?.getGenres
405+
?.firstOrNull()
406+
?.trim()
407+
.orEmpty()
408+
.ifEmpty { ETC }
409+
410+
val feeds = novelFeedUiState.feeds.map { feed ->
411+
Feed(
412+
feed.copy(
413+
novel = feed.novel.copy(
414+
genre = genre,
415+
),
416+
),
417+
)
418+
}
384419
when (novelFeedUiState.isLoadable) {
385420
true -> feedAdapter.submitList(feeds + Loading)
386421
false -> feedAdapter.submitList(feeds)
@@ -405,6 +440,7 @@ class NovelFeedFragment : BaseFragment<FragmentNovelFeedBinding>(R.layout.fragme
405440

406441
companion object {
407442
private const val NOVEL_ID = "NOVEL_ID"
443+
private const val ETC = "기타"
408444

409445
fun newInstance(novelId: Long): NovelFeedFragment =
410446
NovelFeedFragment().also {

app/src/main/res/layout/item_feed.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@
165165
android:layout_marginEnd="20dp"
166166
android:background="@drawable/btn_feed_novel_info"
167167
android:onClick="@{() -> onClick.onNovelInfoClick(feed.novel.id)}"
168+
app:dynamicBackgroundColor="@{feed.novel.novelBackgroundColor}"
169+
app:genreName="@{feed.novel.genre}"
168170
app:layout_constraintEnd_toEndOf="parent"
169171
app:layout_constraintStart_toStartOf="parent"
170172
app:layout_constraintTop_toBottomOf="@+id/iv_feed_image">
@@ -177,6 +179,7 @@
177179
android:layout_marginTop="16dp"
178180
android:layout_marginBottom="16dp"
179181
android:src="@drawable/ic_link"
182+
app:imageTint="@{feed.novel.novelIconColor}"
180183
app:layout_constraintBottom_toBottomOf="parent"
181184
app:layout_constraintStart_toStartOf="parent"
182185
app:layout_constraintTop_toTopOf="parent" />

app/src/main/res/values/colors.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,29 @@
2626
<drawable name="selected_bg">@drawable/bg_selected_gradient</drawable>
2727

2828
<color name="transparent">#00000000</color>
29+
30+
<!-- Genre Icon Colors -->
31+
<color name="romance_FF3378">#FF3378</color>
32+
<color name="romance_fantasy_873EFF">#873EFF</color>
33+
<color name="bl_02A8A4">#02A8A4</color>
34+
<color name="fantasy_4D63FF">#4D63FF</color>
35+
<color name="modern_fantasy_525CDE">#525CDE</color>
36+
<color name="wuxia_E05727">#E05727</color>
37+
<color name="drama_02A8A4">#02A8A4</color>
38+
<color name="mystery_AD55EC">#AD55EC</color>
39+
<color name="light_novel_34C2EB">#34C2EB</color>
40+
41+
<!-- Genre Background Colors -->
42+
<color name="romance_FFE5ED">#FFE5ED</color>
43+
<color name="romance_fantasy_F1E8FF">#F1E8FF</color>
44+
<color name="bl_E0F6F5">#E0F6F5</color>
45+
<color name="fantasy_E9ECFF">#E9ECFF</color>
46+
<color name="modern_fantasy_EAEBFF">#EAEBFF</color>
47+
<color name="wuxia_FFF1EB">#FFF1EB</color>
48+
<color name="drama_E0F6F5">#E0F6F5</color>
49+
<color name="mystery_F6EEFC">#F6EEFC</color>
50+
<color name="light_novel_E6F8FD">#E6F8FD</color>
51+
52+
<color name="etc_6457FC">#6457FC</color>
53+
<color name="etc_FFFFFF">#FFFFFF</color>
2954
</resources>

feature/feed/src/main/java/com/into/websoso/feature/feed/FeedScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.into.websoso.feature.feed
22

3+
import android.annotation.SuppressLint
34
import androidx.compose.foundation.Image
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Box
@@ -45,6 +46,7 @@ import com.into.websoso.feature.feed.model.FeedTab
4546
import com.into.websoso.feature.feed.model.MyFeedFilter
4647
import com.into.websoso.feature.feed.model.SosoFeedType
4748

49+
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
4850
@OptIn(ExperimentalMaterial3Api::class)
4951
@Composable
5052
internal fun FeedScreen(

0 commit comments

Comments
 (0)