diff --git a/app/src/main/kotlin/com/flxrs/dankchat/chat/ChatAdapter.kt b/app/src/main/kotlin/com/flxrs/dankchat/chat/ChatAdapter.kt index 875f9039..2b752969 100644 --- a/app/src/main/kotlin/com/flxrs/dankchat/chat/ChatAdapter.kt +++ b/app/src/main/kotlin/com/flxrs/dankchat/chat/ChatAdapter.kt @@ -243,7 +243,7 @@ class ChatAdapter( val firstHighlightType = message.highlights.firstOrNull()?.type val shouldHighlight = firstHighlightType == HighlightType.Subscription || firstHighlightType == HighlightType.Announcement val background = when { - shouldHighlight -> ContextCompat.getColor(context, R.color.color_sub_highlight) + shouldHighlight -> message.highlights.toBackgroundColor(context) appearanceSettings.checkeredMessages && holder.isAlternateBackground -> MaterialColors.layer( this, android.R.attr.colorBackground, @@ -358,7 +358,7 @@ class ChatAdapter( private fun TextView.handlePointRedemptionMessage(message: PointRedemptionMessage, holder: ViewHolder) { val appearanceSettings = appearanceSettingsDataStore.current() val chatSettings = chatSettingsDataStore.current() - val background = ContextCompat.getColor(context, R.color.color_redemption_highlight) + val background = message.highlights.toBackgroundColor(context) holder.binding.itemLayout.setBackgroundColor(background) setRippleBackground(background, enableRipple = false) @@ -894,6 +894,9 @@ class ChatAdapter( @ColorInt private fun Set.toBackgroundColor(context: Context): Int { val highlight = highestPriorityHighlight() ?: return ContextCompat.getColor(context, android.R.color.transparent) + if (highlight.customColor != null) { + return highlight.customColor + } return when (highlight.type) { HighlightType.Subscription, HighlightType.Announcement -> ContextCompat.getColor(context, R.color.color_sub_highlight) HighlightType.ChannelPointRedemption -> ContextCompat.getColor(context, R.color.color_redemption_highlight) diff --git a/app/src/main/kotlin/com/flxrs/dankchat/data/repo/HighlightsRepository.kt b/app/src/main/kotlin/com/flxrs/dankchat/data/repo/HighlightsRepository.kt index 6dceb31b..918a2cd8 100644 --- a/app/src/main/kotlin/com/flxrs/dankchat/data/repo/HighlightsRepository.kt +++ b/app/src/main/kotlin/com/flxrs/dankchat/data/repo/HighlightsRepository.kt @@ -206,12 +206,14 @@ class HighlightsRepository( val messageHighlights = validMessageHighlights.value val highlights = buildSet { - if (isSub && messageHighlights.areSubsEnabled) { - add(Highlight(HighlightType.Subscription)) + val subsHighlight = messageHighlights.subsHighlight + if (isSub && subsHighlight != null) { + add(Highlight(HighlightType.Subscription, subsHighlight.customColor)) } - if (isAnnouncement && messageHighlights.areAnnouncementsEnabled) { - add(Highlight(HighlightType.Announcement)) + val announcementsHighlight = messageHighlights.announcementsHighlight + if (isAnnouncement && announcementsHighlight != null) { + add(Highlight(HighlightType.Announcement, announcementsHighlight.customColor)) } } @@ -222,15 +224,11 @@ class HighlightsRepository( } private fun PointRedemptionMessage.calculateHighlightState(): PointRedemptionMessage { - val redemptionsEnabled = validMessageHighlights.value - .any { it.type == MessageHighlightEntityType.ChannelPointRedemption } - - val highlights = when { - redemptionsEnabled -> setOf(Highlight(HighlightType.ChannelPointRedemption)) - else -> emptySet() + val rewardsHighlight = validMessageHighlights.value.rewardsHighlight + if (rewardsHighlight != null) { + return copy(highlights = setOf(Highlight(HighlightType.ChannelPointRedemption, rewardsHighlight.customColor))) } - - return copy(highlights = highlights) + return copy(highlights = emptySet()) } private fun PrivMessage.calculateHighlightState(): PrivMessage { @@ -247,30 +245,35 @@ class HighlightsRepository( val badgeHighlights = validBadgeHighlights.value val messageHighlights = validMessageHighlights.value val highlights = buildSet { - if (isSub && messageHighlights.areSubsEnabled) { - add(Highlight(HighlightType.Subscription)) + val subsHighlight = messageHighlights.subsHighlight + if (isSub && subsHighlight != null) { + add(Highlight(HighlightType.Subscription, subsHighlight.customColor)) } - if (isAnnouncement && messageHighlights.areAnnouncementsEnabled) { - add(Highlight(HighlightType.Announcement)) + val announcementsHighlight = messageHighlights.announcementsHighlight + if (isAnnouncement && announcementsHighlight != null) { + add(Highlight(HighlightType.Announcement, announcementsHighlight.customColor)) } - if (isReward && messageHighlights.areRewardsEnabled) { - add(Highlight(HighlightType.ChannelPointRedemption)) + val rewardsHighlight = messageHighlights.rewardsHighlight + if (isReward && rewardsHighlight != null) { + add(Highlight(HighlightType.ChannelPointRedemption, rewardsHighlight.customColor)) } - if (isFirstMessage && messageHighlights.areFirstMessagesEnabled) { - add(Highlight(HighlightType.FirstMessage)) + val firstMessageHighlight = messageHighlights.firstMessageHighlight + if (isFirstMessage && firstMessageHighlight != null) { + add(Highlight(HighlightType.FirstMessage, firstMessageHighlight.customColor)) } - if (isElevatedMessage && messageHighlights.areElevatedMessagesEnabled) { - add(Highlight(HighlightType.ElevatedMessage)) + val elevatedMessageHighlight = messageHighlights.elevatedMessageHighlight + if (isElevatedMessage && elevatedMessageHighlight != null) { + add(Highlight(HighlightType.ElevatedMessage, elevatedMessageHighlight.customColor)) } if (containsCurrentUserName) { val highlight = messageHighlights.userNameHighlight if (highlight?.enabled == true) { - add(Highlight(HighlightType.Username)) + add(Highlight(HighlightType.Username, highlight.customColor)) addNotificationHighlightIfEnabled(highlight) } } @@ -278,7 +281,7 @@ class HighlightsRepository( if (containsParticipatedReply) { val highlight = messageHighlights.repliesHighlight if (highlight?.enabled == true) { - add(Highlight(HighlightType.Reply)) + add(Highlight(HighlightType.Reply, highlight.customColor)) addNotificationHighlightIfEnabled(highlight) } } @@ -289,14 +292,14 @@ class HighlightsRepository( val regex = it.regex ?: return@forEach if (message.contains(regex)) { - add(Highlight(HighlightType.Custom)) + add(Highlight(HighlightType.Custom, it.customColor)) addNotificationHighlightIfEnabled(it) } } userHighlights.forEach { if (name.matches(it.username)) { - add(Highlight(HighlightType.Custom)) + add(Highlight(HighlightType.Custom, it.customColor)) addNotificationHighlightIfEnabled(it) } } @@ -326,20 +329,20 @@ class HighlightsRepository( else -> this } - private val List.areSubsEnabled: Boolean - get() = isMessageHighlightTypeEnabled(MessageHighlightEntityType.Subscription) + private val List.subsHighlight: MessageHighlightEntity? + get() = find { it.type == MessageHighlightEntityType.Subscription } - private val List.areAnnouncementsEnabled: Boolean - get() = isMessageHighlightTypeEnabled(MessageHighlightEntityType.Announcement) + private val List.announcementsHighlight : MessageHighlightEntity? + get() = find { it.type == MessageHighlightEntityType.Announcement } - private val List.areRewardsEnabled: Boolean - get() = isMessageHighlightTypeEnabled(MessageHighlightEntityType.ChannelPointRedemption) + private val List.rewardsHighlight: MessageHighlightEntity? + get() = find { it.type == MessageHighlightEntityType.ChannelPointRedemption } - private val List.areFirstMessagesEnabled: Boolean - get() = isMessageHighlightTypeEnabled(MessageHighlightEntityType.FirstMessage) + private val List.firstMessageHighlight: MessageHighlightEntity? + get() = find { it.type == MessageHighlightEntityType.FirstMessage } - private val List.areElevatedMessagesEnabled: Boolean - get() = isMessageHighlightTypeEnabled(MessageHighlightEntityType.ElevatedMessage) + private val List.elevatedMessageHighlight: MessageHighlightEntity? + get() = find { it.type == MessageHighlightEntityType.ElevatedMessage } private val List.repliesHighlight: MessageHighlightEntity? get() = find { it.type == MessageHighlightEntityType.Reply } @@ -347,10 +350,6 @@ class HighlightsRepository( private val List.userNameHighlight: MessageHighlightEntity? get() = find { it.type == MessageHighlightEntityType.Username } - private fun List.isMessageHighlightTypeEnabled(type: MessageHighlightEntityType): Boolean { - return any { it.type == type } - } - private fun MutableCollection.addNotificationHighlightIfEnabled(highlightEntity: MessageHighlightEntity) { if (highlightEntity.createNotification) { add(Highlight(HighlightType.Notification)) diff --git a/app/src/main/kotlin/com/flxrs/dankchat/data/repo/chat/ChatRepository.kt b/app/src/main/kotlin/com/flxrs/dankchat/data/repo/chat/ChatRepository.kt index ae8477ae..a06a26c5 100644 --- a/app/src/main/kotlin/com/flxrs/dankchat/data/repo/chat/ChatRepository.kt +++ b/app/src/main/kotlin/com/flxrs/dankchat/data/repo/chat/ChatRepository.kt @@ -653,7 +653,7 @@ class ChatRepository( } reward?.let { - listOf(ChatItem(PointRedemptionMessage.parsePointReward(it.timestamp, it.data))) + listOf(ChatItem(PointRedemptionMessage.parsePointReward(it.timestamp, it.data).calculateHighlightState())) }.orEmpty() } diff --git a/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightItem.kt b/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightItem.kt index 8f4ac0d9..e29f6f17 100644 --- a/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightItem.kt +++ b/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightItem.kt @@ -21,6 +21,7 @@ data class MessageHighlightItem( val createNotification: Boolean, val loggedIn: Boolean, val notificationsEnabled: Boolean, + val customColor: Int?, ) : HighlightItem { enum class Type { Username, @@ -46,6 +47,7 @@ data class UserHighlightItem( val username: String, val createNotification: Boolean, val notificationsEnabled: Boolean, + val customColor: Int?, ) : HighlightItem data class BadgeHighlightItem( @@ -75,6 +77,7 @@ fun MessageHighlightEntity.toItem(loggedIn: Boolean, notificationsEnabled: Boole createNotification = createNotification, loggedIn = loggedIn, notificationsEnabled = notificationsEnabled, + customColor = customColor, ) fun MessageHighlightItem.toEntity() = MessageHighlightEntity( @@ -85,6 +88,7 @@ fun MessageHighlightItem.toEntity() = MessageHighlightEntity( isRegex = isRegex, isCaseSensitive = isCaseSensitive, createNotification = createNotification, + customColor = customColor, ) fun MessageHighlightItem.Type.toEntityType(): MessageHighlightEntityType = when (this) { @@ -115,6 +119,7 @@ fun UserHighlightEntity.toItem(notificationsEnabled: Boolean) = UserHighlightIte username = username, createNotification = createNotification, notificationsEnabled = notificationsEnabled, + customColor = customColor, ) fun UserHighlightItem.toEntity() = UserHighlightEntity( @@ -122,6 +127,7 @@ fun UserHighlightItem.toEntity() = UserHighlightEntity( enabled = enabled, username = username, createNotification = createNotification, + customColor = customColor, ) fun BadgeHighlightEntity.toItem(notificationsEnabled: Boolean) = BadgeHighlightItem( diff --git a/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightsScreen.kt b/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightsScreen.kt index 98c1ae63..dca3540e 100644 --- a/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightsScreen.kt +++ b/app/src/main/kotlin/com/flxrs/dankchat/preferences/notifications/highlights/HighlightsScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListState @@ -24,18 +25,22 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FabPosition import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.ScaffoldDefaults @@ -44,15 +49,22 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -61,6 +73,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flxrs.dankchat.R @@ -69,6 +83,7 @@ import com.flxrs.dankchat.preferences.components.DankBackground import com.flxrs.dankchat.preferences.components.NavigationBarSpacer import com.flxrs.dankchat.preferences.components.PreferenceTabRow import com.flxrs.dankchat.utils.compose.animatedAppBarColor +import com.rarepebble.colorpicker.ColorPickerView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flowOn @@ -429,6 +444,78 @@ private fun MessageHighlightItem( ) } } + val defaultColor = when(item.type) { + MessageHighlightItem.Type.Subscription, MessageHighlightItem.Type.Announcement -> ContextCompat.getColor(LocalContext.current, R.color.color_sub_highlight) + MessageHighlightItem.Type.ChannelPointRedemption -> ContextCompat.getColor(LocalContext.current, R.color.color_redemption_highlight) + MessageHighlightItem.Type.ElevatedMessage -> ContextCompat.getColor(LocalContext.current, R.color.color_elevated_message_highlight) + MessageHighlightItem.Type.FirstMessage -> ContextCompat.getColor(LocalContext.current, R.color.color_first_message_highlight) + MessageHighlightItem.Type.Username -> ContextCompat.getColor(LocalContext.current, R.color.color_mention_highlight) + MessageHighlightItem.Type.Reply, MessageHighlightItem.Type.Custom -> ContextCompat.getColor(LocalContext.current, R.color.color_mention_highlight) + } + val color = item.customColor ?: defaultColor + var showColorPicker by remember { mutableStateOf(false) } + var selectedColor by remember(color) { mutableIntStateOf(color) } + OutlinedButton( + onClick = { showColorPicker = true }, + enabled = item.enabled, + contentPadding = ButtonDefaults.ButtonWithIconContentPadding, + content = { + Spacer( + Modifier + .size(ButtonDefaults.IconSize) + .background(color = Color(color), shape = CircleShape) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(R.string.choose_highlight_color)) + }, + modifier = Modifier.padding(12.dp) + ) + if (showColorPicker) { + ModalBottomSheet( + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + onDismissRequest = { + onChanged(item.copy(customColor = selectedColor)) + showColorPicker = false + }, + ) { + Text( + text = stringResource(R.string.pick_highlight_color_title), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + ) + Row ( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + onClick = { selectedColor = defaultColor }, + content = { Text(stringResource(R.string.reset_default_highlight_color)) }, + ) + TextButton( + onClick = { selectedColor = color }, + content = { Text(stringResource(R.string.reset)) }, + ) + } + AndroidView( + factory = { context -> + ColorPickerView(context).apply { + showAlpha(true) + setOriginalColor(color) + setCurrentColor(selectedColor) + addColorObserver { + selectedColor = it.color + } + } + }, + update = { + it.setCurrentColor(selectedColor) + } + ) + } + } } } @@ -471,6 +558,71 @@ private fun UserHighlightItem( enabled = item.enabled && item.notificationsEnabled, ) } + val defaultColor = ContextCompat.getColor(LocalContext.current, R.color.color_mention_highlight) + val color = item.customColor ?: defaultColor + var showColorPicker by remember { mutableStateOf(false) } + var selectedColor by remember(color) { mutableIntStateOf(color) } + OutlinedButton( + onClick = { showColorPicker = true }, + enabled = item.enabled, + contentPadding = ButtonDefaults.ButtonWithIconContentPadding, + content = { + Spacer( + Modifier + .size(ButtonDefaults.IconSize) + .background(color = Color(color), shape = CircleShape) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(R.string.choose_highlight_color)) + }, + modifier = Modifier.padding(12.dp) + ) + if (showColorPicker) { + ModalBottomSheet( + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + onDismissRequest = { + onChanged(item.copy(customColor = selectedColor)) + showColorPicker = false + }, + ) { + Text( + text = stringResource(R.string.pick_highlight_color_title), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + ) + Row ( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + onClick = { selectedColor = defaultColor }, + content = { Text(stringResource(R.string.reset_default_highlight_color)) }, + ) + TextButton( + onClick = { selectedColor = color }, + content = { Text(stringResource(R.string.reset)) }, + ) + } + AndroidView( + factory = { context -> + ColorPickerView(context).apply { + showAlpha(true) + setOriginalColor(color) + setCurrentColor(selectedColor) + addColorObserver { + selectedColor = it.color + } + } + }, + update = { + it.setCurrentColor(selectedColor) + } + ) + } + } } IconButton( onClick = onRemove, @@ -545,6 +697,71 @@ private fun BadgeHighlightItem( enabled = item.enabled && item.notificationsEnabled, ) } + val defaultColor = ContextCompat.getColor(LocalContext.current, R.color.color_mention_highlight) + val color = item.customColor ?: defaultColor + var showColorPicker by remember { mutableStateOf(false) } + var selectedColor by remember(color) { mutableIntStateOf(color) } + OutlinedButton( + onClick = { showColorPicker = true }, + enabled = item.enabled, + contentPadding = ButtonDefaults.ButtonWithIconContentPadding, + content = { + Spacer( + Modifier + .size(ButtonDefaults.IconSize) + .background(color = Color(color), shape = CircleShape) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(R.string.choose_highlight_color)) + }, + modifier = Modifier.padding(12.dp) + ) + if (showColorPicker) { + ModalBottomSheet( + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + onDismissRequest = { + onChanged(item.copy(customColor = selectedColor)) + showColorPicker = false + }, + ) { + Text( + text = stringResource(R.string.pick_highlight_color_title), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + ) + Row ( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + TextButton( + onClick = { selectedColor = defaultColor }, + content = { Text(stringResource(R.string.reset_default_highlight_color)) }, + ) + TextButton( + onClick = { selectedColor = color }, + content = { Text(stringResource(R.string.reset)) }, + ) + } + AndroidView( + factory = { context -> + ColorPickerView(context).apply { + showAlpha(true) + setOriginalColor(color) + setCurrentColor(selectedColor) + addColorObserver { + selectedColor = it.color + } + } + }, + update = { + it.setCurrentColor(selectedColor) + } + ) + } + } } if (item.isCustom) { IconButton( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 846ad8ce..405ac596 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -467,4 +467,7 @@ VIP Founder Subscriber + Pick custom highlight color + Default + Choose Color