diff --git a/app/src/main/kotlin/com/bff/wespot/AppNavGraphs.kt b/app/src/main/kotlin/com/bff/wespot/AppNavGraphs.kt index 5c797c4b..886ea29e 100644 --- a/app/src/main/kotlin/com/bff/wespot/AppNavGraphs.kt +++ b/app/src/main/kotlin/com/bff/wespot/AppNavGraphs.kt @@ -14,6 +14,7 @@ import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph import androidx.navigation.NavHostController import com.bff.wespot.entire.screen.screen.destinations.AccountSettingScreenDestination +import com.bff.wespot.entire.screen.screen.destinations.BlockListScreenDestination import com.bff.wespot.entire.screen.screen.destinations.EntireScreenDestination import com.bff.wespot.entire.screen.screen.destinations.NotificationSettingScreenDestination import com.bff.wespot.entire.screen.screen.destinations.RevokeConfirmScreenDestination @@ -25,6 +26,7 @@ import com.bff.wespot.message.screen.destinations.MessageWriteScreenDestination import com.bff.wespot.message.screen.destinations.ReceiverSelectionScreenDestination import com.bff.wespot.message.screen.destinations.ReservedMessageScreenDestination import com.bff.wespot.message.viewmodel.SendViewModel +import com.bff.wespot.navigation.Navigator import com.bff.wespot.vote.screen.destinations.CharacterSettingScreenDestination import com.bff.wespot.vote.screen.destinations.IndividualVoteScreenDestination import com.bff.wespot.vote.screen.destinations.IntroductionScreenDestination @@ -87,6 +89,7 @@ object AppNavGraphs { AccountSettingScreenDestination, RevokeScreenDestination, RevokeConfirmScreenDestination, + BlockListScreenDestination, ).routedIn(this) .associateBy { it.route } } @@ -144,6 +147,7 @@ fun DestinationScopeWithNoDependencies<*>.currentNavigator(): CommonNavGraphNavi @Composable internal fun AppNavigation( navController: NavHostController, + navigator: Navigator, modifier: Modifier = Modifier, ) { val engine = rememberNavHostEngine( @@ -163,6 +167,7 @@ internal fun AppNavigation( modifier = modifier, dependenciesContainerBuilder = { dependency(currentNavigator()) + dependency(navigator) dependency(sendViewModel) dependency(votingViewModel) }, diff --git a/app/src/main/kotlin/com/bff/wespot/CommonNavGraphNavigator.kt b/app/src/main/kotlin/com/bff/wespot/CommonNavGraphNavigator.kt index 1466ad10..c91aac3d 100644 --- a/app/src/main/kotlin/com/bff/wespot/CommonNavGraphNavigator.kt +++ b/app/src/main/kotlin/com/bff/wespot/CommonNavGraphNavigator.kt @@ -2,10 +2,12 @@ package com.bff.wespot import androidx.navigation.NavController import com.bff.wespot.entire.screen.screen.AccountSettingNavigator +import com.bff.wespot.entire.screen.screen.BlockListNavigator import com.bff.wespot.entire.screen.screen.EntireNavigator import com.bff.wespot.entire.screen.screen.NotificationSettingNavigator import com.bff.wespot.entire.screen.screen.SettingNavigator import com.bff.wespot.entire.screen.screen.destinations.AccountSettingScreenDestination +import com.bff.wespot.entire.screen.screen.destinations.BlockListScreenDestination import com.bff.wespot.entire.screen.screen.destinations.NotificationSettingScreenDestination import com.bff.wespot.entire.screen.screen.destinations.RevokeConfirmScreenDestination import com.bff.wespot.entire.screen.screen.destinations.RevokeScreenDestination @@ -61,6 +63,7 @@ class CommonNavGraphNavigator( AccountSettingNavigator, RevokeNavigator, RevokeConfirmNavigator, + BlockListNavigator, VotingNavigator, VoteResultNavigator, VoteStorageNavigator, @@ -139,6 +142,10 @@ class CommonNavGraphNavigator( navController.navigate(IndividualVoteScreenDestination(args) within navGraph) } + override fun navigateToBlockListScreen() { + navController.navigate(BlockListScreenDestination within navGraph) + } + override fun navigateToCharacterScreen() { navController.navigate(CharacterSettingScreenDestination within navGraph) } diff --git a/app/src/main/kotlin/com/bff/wespot/MainActivity.kt b/app/src/main/kotlin/com/bff/wespot/MainActivity.kt index db8fcc9c..5142cd18 100644 --- a/app/src/main/kotlin/com/bff/wespot/MainActivity.kt +++ b/app/src/main/kotlin/com/bff/wespot/MainActivity.kt @@ -40,19 +40,24 @@ import com.bff.wespot.designsystem.component.header.WSTopBar import com.bff.wespot.designsystem.theme.StaticTypeScale import com.bff.wespot.designsystem.theme.WeSpotTheme import com.bff.wespot.designsystem.theme.WeSpotThemeManager +import com.bff.wespot.navigation.Navigator import com.ramcosta.composedestinations.navigation.navigate import com.ramcosta.composedestinations.spec.NavGraphSpec import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject + lateinit var navigator: Navigator + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { WeSpotTheme { - MainScreen() + MainScreen(navigator) } } } @@ -60,7 +65,7 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun MainScreen() { +private fun MainScreen(navigator: Navigator) { val navController = rememberNavController() val checkScreen by navController.checkCurrentScreen() @@ -118,7 +123,7 @@ private fun MainScreen() { } }, ) { - AppNavigation(navController = navController, modifier = Modifier.padding(it)) + AppNavigation(navController = navController, navigator, modifier = Modifier.padding(it)) } } diff --git a/core/model/src/main/kotlin/com/bff/wespot/model/message/Sender.kt b/core/model/src/main/kotlin/com/bff/wespot/model/message/Sender.kt new file mode 100644 index 00000000..1ef7d0db --- /dev/null +++ b/core/model/src/main/kotlin/com/bff/wespot/model/message/Sender.kt @@ -0,0 +1,9 @@ +package com.bff.wespot.model.message + +data class Sender( + val id: Int, + val backgroundColor: String, + val iconUrl: String, +) { + constructor() : this(-1, "", "") +} diff --git a/core/model/src/main/kotlin/com/bff/wespot/model/message/response/BlockedMessage.kt b/core/model/src/main/kotlin/com/bff/wespot/model/message/response/BlockedMessage.kt new file mode 100644 index 00000000..7039e67e --- /dev/null +++ b/core/model/src/main/kotlin/com/bff/wespot/model/message/response/BlockedMessage.kt @@ -0,0 +1,19 @@ +package com.bff.wespot.model.message.response + +import com.bff.wespot.model.message.Sender +import com.bff.wespot.model.user.response.User +import java.time.LocalDateTime + +data class BlockedMessage( + val id: Int, + val senderName: String, + val senderProfile: Sender, + val receiver: User, + val content: String, + val receivedAt: LocalDateTime?, + val isRead: Boolean, + val isReported: Boolean, + val isBlocked: Boolean, +) { + constructor() : this(-1, "", Sender(), User(), "", LocalDateTime.MIN, false, false, false) +} diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/IntroductionScreen.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/IntroductionScreen.kt index ab2b0f0d..44b48af3 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/IntroductionScreen.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/IntroductionScreen.kt @@ -60,7 +60,7 @@ fun IntroductionScreen( modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp), - contentAlignment = Alignment.CenterEnd + contentAlignment = Alignment.CenterEnd, ) { LetterCountIndicator(currentCount = introduction.length, maxCount = 20) } diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/ReservedMessageItem.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/ReservedMessageItem.kt new file mode 100644 index 00000000..46cae4e5 --- /dev/null +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/ReservedMessageItem.kt @@ -0,0 +1,111 @@ +package com.bff.wespot.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.bff.wespot.designsystem.theme.Gray400 +import com.bff.wespot.designsystem.theme.Gray600 +import com.bff.wespot.designsystem.theme.StaticTypeScale +import com.bff.wespot.designsystem.theme.WeSpotThemeManager +import com.bff.wespot.util.hexToColor + +@Composable +fun ReservedMessageItem( + title: String, + subTitle: String, + backgroundColor: String, + iconUrl: String, + chipText: String, + chipEnabled: Boolean = true, + chipDisabledText: String = "", + onClick: () -> Unit, +) { + Column { + Row( + modifier = Modifier.padding(start = 18.dp, end = 18.dp, top = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(42.dp) + .clip(CircleShape) + .background( + runCatching { + hexToColor(backgroundColor) + }.getOrDefault(WeSpotThemeManager.colors.cardBackgroundColor), + ), + contentAlignment = Alignment.Center, + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(iconUrl) + .crossfade(true) + .build(), + contentDescription = stringResource(com.bff.wespot.ui.R.string.user_character_image), + ) + } + + Column( + modifier = Modifier + .padding(horizontal = 10.dp) + .weight(1f), + ) { + Text( + text = title, + style = StaticTypeScale.Default.body6, + color = WeSpotThemeManager.colors.txtSubColor, + ) + + Text( + text = subTitle, + style = StaticTypeScale.Default.body6, + color = WeSpotThemeManager.colors.txtTitleColor, + ) + } + + FilterChip( + shape = WeSpotThemeManager.shapes.extraLarge, + onClick = { onClick() }, + selected = false, + label = { + Text( + text = if (chipEnabled) chipText else chipDisabledText, + style = StaticTypeScale.Default.body6, + ) + }, + enabled = chipEnabled, + border = null, + colors = FilterChipDefaults.filterChipColors( + containerColor = WeSpotThemeManager.colors.secondaryBtnColor, + labelColor = Color(0xFFF7F7F8), + disabledContainerColor = Gray600, + disabledLabelColor = Gray400, + ), + ) + } + + HorizontalDivider( + modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp), + thickness = 1.dp, + color = WeSpotThemeManager.colors.cardBackgroundColor, + ) + } +} diff --git a/core/ui/src/main/kotlin/com/bff/wespot/ui/WSListItem.kt b/core/ui/src/main/kotlin/com/bff/wespot/ui/WSListItem.kt index 8ac5f73e..abb8275d 100644 --- a/core/ui/src/main/kotlin/com/bff/wespot/ui/WSListItem.kt +++ b/core/ui/src/main/kotlin/com/bff/wespot/ui/WSListItem.kt @@ -1,6 +1,5 @@ package com.bff.wespot.ui -import android.graphics.Color.parseColor import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -21,7 +20,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -32,6 +30,7 @@ import com.bff.wespot.designsystem.theme.StaticTypeScale import com.bff.wespot.designsystem.theme.WeSpotTheme import com.bff.wespot.designsystem.theme.WeSpotThemeManager import com.bff.wespot.designsystem.util.OrientationPreviews +import com.bff.wespot.util.hexToColor @Composable fun WSListItem( @@ -69,7 +68,7 @@ fun WSListItem( .clip(CircleShape) .background( if (backgroundColor.isNotEmpty()) { - Color(parseColor(backgroundColor)) + hexToColor(backgroundColor) } else { WeSpotThemeManager.colors.cardBackgroundColor }, diff --git a/core/ui/src/main/res/values/string.xml b/core/ui/src/main/res/values/string.xml deleted file mode 100644 index 22ec7b9b..00000000 --- a/core/ui/src/main/res/values/string.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - school icon - \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index aec778e9..143a4d89 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ User Character Image + 수정하기 + school icon %1$s님을 잘 나타낼 수 있는 \n캐릭터를 선택해 주세요 캐릭터 고르기 배경 색 고르기 @@ -8,4 +10,4 @@ 친구들에게 %1$s을 소개하는 한 줄을 작성해주세요 안녕 나는 1반의 비타민 작성 완료 - \ No newline at end of file + diff --git a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/BlockedMessageDto.kt b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/BlockedMessageDto.kt new file mode 100644 index 00000000..6dfc88d5 --- /dev/null +++ b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/BlockedMessageDto.kt @@ -0,0 +1,36 @@ +package com.bff.wespot.data.remote.model.message.response + +import com.bff.wespot.data.remote.extensions.toISOLocalDateTime +import com.bff.wespot.data.remote.model.user.response.UserDto +import com.bff.wespot.model.message.response.BlockedMessage +import kotlinx.serialization.Serializable + +@Serializable +data class BlockedMessageListDto( + val messages: List, +) + +@Serializable +data class BlockedMessageDto( + val id: Int, + val senderName: String, + val senderProfile: SenderDto, + val receiver: UserDto, + val content: String, + val receivedAt: String?, + val isRead: Boolean, + val isReported: Boolean, + val isBlocked: Boolean, +) { + fun toBlockedMessage(): BlockedMessage = BlockedMessage( + id = id, + senderName = senderName, + senderProfile = senderProfile.toSender(), + receiver = receiver.toUser(), + content = content, + receivedAt = receivedAt?.toISOLocalDateTime(), + isRead = isRead, + isReported = isReported, + isBlocked = isBlocked, + ) +} diff --git a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/SenderDto.kt b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/SenderDto.kt new file mode 100644 index 00000000..8227af7c --- /dev/null +++ b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/model/message/response/SenderDto.kt @@ -0,0 +1,17 @@ +package com.bff.wespot.data.remote.model.message.response + +import com.bff.wespot.model.message.Sender +import kotlinx.serialization.Serializable + +@Serializable +data class SenderDto ( + val id: Int, + val backgroundColor: String, + val iconUrl: String, +) { + fun toSender(): Sender = Sender( + id = id, + backgroundColor = backgroundColor, + iconUrl = iconUrl, + ) +} \ No newline at end of file diff --git a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSource.kt b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSource.kt index 55317c07..d1b2f7a3 100644 --- a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSource.kt +++ b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSource.kt @@ -2,10 +2,12 @@ package com.bff.wespot.data.remote.source.message import com.bff.wespot.data.remote.model.message.request.MessageTypeDto import com.bff.wespot.data.remote.model.message.request.SentMessageDto +import com.bff.wespot.data.remote.model.message.response.BlockedMessageListDto import com.bff.wespot.data.remote.model.message.response.MessageDto import com.bff.wespot.data.remote.model.message.response.MessageIdDto import com.bff.wespot.data.remote.model.message.response.MessageListDto import com.bff.wespot.data.remote.model.message.response.MessageStatusDto +import com.bff.wespot.model.message.response.Message interface MessageDataSource { suspend fun getMessageList( @@ -20,4 +22,6 @@ interface MessageDataSource { suspend fun editMessage(messageId: Int, sentMessageDto: SentMessageDto): Result suspend fun getMessage(messageId: Int): Result + + suspend fun getBlockedMessage(cursorId: Int): Result } diff --git a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSourceImpl.kt b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSourceImpl.kt index e05759b8..85734133 100644 --- a/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSourceImpl.kt +++ b/data-remote/src/main/kotlin/com/bff/wespot/data/remote/source/message/MessageDataSourceImpl.kt @@ -3,6 +3,7 @@ package com.bff.wespot.data.remote.source.message import com.bff.wespot.data.remote.model.message.request.MessageTypeDto import com.bff.wespot.data.remote.model.message.request.SentMessageDto import com.bff.wespot.data.remote.model.message.request.type +import com.bff.wespot.data.remote.model.message.response.BlockedMessageListDto import com.bff.wespot.data.remote.model.message.response.MessageDto import com.bff.wespot.data.remote.model.message.response.MessageIdDto import com.bff.wespot.data.remote.model.message.response.MessageListDto @@ -66,4 +67,13 @@ class MessageDataSourceImpl @Inject constructor( path("messages/$messageId") } } + + override suspend fun getBlockedMessage(cursorId: Int): Result = + httpClient.safeRequest { + url { + method = HttpMethod.Get + path("messages/blocked") + parameter("cursorId", cursorId) + } + } } diff --git a/data/src/main/kotlin/com/bff/wespot/data/repository/message/MessageRepositoryImpl.kt b/data/src/main/kotlin/com/bff/wespot/data/repository/message/MessageRepositoryImpl.kt index 8086cd22..8383f5a2 100644 --- a/data/src/main/kotlin/com/bff/wespot/data/repository/message/MessageRepositoryImpl.kt +++ b/data/src/main/kotlin/com/bff/wespot/data/repository/message/MessageRepositoryImpl.kt @@ -2,13 +2,14 @@ package com.bff.wespot.data.repository.message import com.bff.wespot.data.mapper.message.toMessageTypeDto import com.bff.wespot.data.mapper.message.toSentMessageDto -import com.bff.wespot.data.remote.source.message.MessageDataSource import com.bff.wespot.domain.repository.message.MessageRepository import com.bff.wespot.model.message.request.MessageType import com.bff.wespot.model.message.request.SentMessage +import com.bff.wespot.model.message.response.MessageStatus +import com.bff.wespot.data.remote.source.message.MessageDataSource +import com.bff.wespot.model.message.response.BlockedMessage import com.bff.wespot.model.message.response.Message import com.bff.wespot.model.message.response.MessageList -import com.bff.wespot.model.message.response.MessageStatus import javax.inject.Inject class MessageRepositoryImpl @Inject constructor( @@ -43,4 +44,9 @@ class MessageRepositoryImpl @Inject constructor( messageDataSource.getMessage(messageId).mapCatching { messageDto -> messageDto.toMessage() } + + override suspend fun getBlockedMessage(cursorId: Int): Result> = + messageDataSource.getBlockedMessage(cursorId).mapCatching { listDto -> + listDto.messages.map { it.toBlockedMessage() } + } } diff --git a/domain/src/main/kotlin/com/bff/wespot/domain/repository/message/MessageRepository.kt b/domain/src/main/kotlin/com/bff/wespot/domain/repository/message/MessageRepository.kt index 2f1fc11e..ae24b824 100644 --- a/domain/src/main/kotlin/com/bff/wespot/domain/repository/message/MessageRepository.kt +++ b/domain/src/main/kotlin/com/bff/wespot/domain/repository/message/MessageRepository.kt @@ -2,6 +2,7 @@ package com.bff.wespot.domain.repository.message import com.bff.wespot.model.message.request.MessageType import com.bff.wespot.model.message.request.SentMessage +import com.bff.wespot.model.message.response.BlockedMessage import com.bff.wespot.model.message.response.Message import com.bff.wespot.model.message.response.MessageList import com.bff.wespot.model.message.response.MessageStatus @@ -16,4 +17,6 @@ interface MessageRepository { suspend fun editMessage(messageId: Int, sentMessage: SentMessage): Result suspend fun getMessage(messageId: Int): Result + + suspend fun getBlockedMessage(cursorId: Int): Result> } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/AccountSettingScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/AccountSettingScreen.kt index 057a36c4..69c20623 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/AccountSettingScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/AccountSettingScreen.kt @@ -27,6 +27,7 @@ import com.bff.wespot.entire.R import com.bff.wespot.entire.screen.state.EntireAction import com.bff.wespot.entire.screen.state.EntireSideEffect import com.bff.wespot.entire.screen.viewmodel.EntireViewModel +import com.bff.wespot.navigation.Navigator import com.ramcosta.composedestinations.annotation.Destination import org.orbitmvi.orbit.compose.collectSideEffect @@ -40,6 +41,7 @@ interface AccountSettingNavigator { @Composable fun AccountSettingScreen( navigator: AccountSettingNavigator, + activityNavigator: Navigator, viewModel: EntireViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -50,7 +52,7 @@ fun AccountSettingScreen( viewModel.collectSideEffect { when (it) { is EntireSideEffect.NavigateToAuth -> { - val intent = it.navigator.navigateToAuth(context) + val intent = activityNavigator.navigateToAuth(context) context.startActivity(intent) } } @@ -90,7 +92,7 @@ fun AccountSettingScreen( cancelButtonText = stringResource(id = R.string.sign_out), okButtonClick = { showDialog = false }, cancelButtonClick = { action(EntireAction.OnSignOutButtonClicked) }, - onDismissRequest = { showDialog = false }, + onDismissRequest = { }, ) } } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/BlockListScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/BlockListScreen.kt new file mode 100644 index 00000000..023096a0 --- /dev/null +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/BlockListScreen.kt @@ -0,0 +1,121 @@ +package com.bff.wespot.entire.screen.screen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.bff.wespot.designsystem.component.header.WSTopBar +import com.bff.wespot.designsystem.component.modal.WSDialog +import com.bff.wespot.designsystem.theme.StaticTypeScale +import com.bff.wespot.designsystem.theme.WeSpotThemeManager +import com.bff.wespot.entire.R +import com.bff.wespot.entire.screen.state.EntireAction +import com.bff.wespot.entire.screen.viewmodel.EntireViewModel +import com.bff.wespot.ui.ReservedMessageItem +import com.ramcosta.composedestinations.annotation.Destination +import org.orbitmvi.orbit.compose.collectAsState + +interface BlockListNavigator { + fun navigateUp() +} + +@OptIn(ExperimentalMaterial3Api::class) +@Destination +@Composable +fun BlockListScreen( + navigator: BlockListNavigator, + viewModel: EntireViewModel = hiltViewModel(), +) { + var showDialog by remember { mutableStateOf(false) } + + val action = viewModel::onAction + val state by viewModel.collectAsState() + + Scaffold( + topBar = { + WSTopBar( + title = "", + canNavigateBack = true, + navigateUp = { navigator.navigateUp() }, + ) + }, + ) { + Column( + modifier = Modifier.padding(it), + ) { + Text( + modifier = Modifier.padding(bottom = 16.dp, start = 24.dp, end = 24.dp), + text = stringResource(R.string.block_list), + style = StaticTypeScale.Default.header1, + color = WeSpotThemeManager.colors.txtTitleColor, + ) + + LazyColumn(verticalArrangement = Arrangement.spacedBy(12.dp)) { + items(state.blockedMessageList, key = { message -> message.id }) { item -> + ReservedMessageItem( + title = stringResource(com.bff.wespot.designsystem.R.string.letter_sender), + subTitle = item.senderName, + backgroundColor = item.senderProfile.backgroundColor, + iconUrl = item.senderProfile.iconUrl, + chipText = stringResource(R.string.unblock), + chipEnabled = item.id !in state.unBlockList, + chipDisabledText = stringResource(R.string.unblock_done), + onClick = { + action(EntireAction.OnUnBlockButtonClicked(item.id)) + showDialog = true + }, + ) + } + } + } + + if (showDialog) { + WSDialog( + title = stringResource(R.string.unblock_dialog_title), + subTitle = "", + okButtonText = stringResource(R.string.close), + cancelButtonText = stringResource(R.string.unblock), + okButtonClick = { showDialog = false }, + cancelButtonClick = { + action(EntireAction.UnBlockMessage) + showDialog = false + }, + onDismissRequest = { }, + ) + } + } + + if (state.isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .clickable(enabled = false) { }, + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator() + } + } + + LaunchedEffect(Unit) { + action(EntireAction.OnBlockListScreenEntered) + } +} diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/EntireScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/EntireScreen.kt index 8d70721d..9f09e6bf 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/EntireScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/EntireScreen.kt @@ -73,8 +73,10 @@ internal fun EntireScreen( navigateUp = { navigator.navigateUp() }, action = { Icon( - modifier = Modifier.clickable { navigator.navigateToSetting() }, - imageVector = ImageVector.vectorResource(R.drawable.setting), + modifier = Modifier + .clickable { navigator.navigateToSetting() } + .padding(end = 16.dp), + imageVector = ImageVector.vectorResource(R.drawable.ic_setting), contentDescription = stringResource(R.string.setting_icon), tint = WeSpotThemeManager.colors.secondaryBtnColor, ) diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/SettingScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/SettingScreen.kt index 91b96eb4..b0ae74fe 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/SettingScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/SettingScreen.kt @@ -26,6 +26,7 @@ interface SettingNavigator { fun navigateUp() fun navigateToNotificationSetting() fun navigateToAccountSetting() + fun navigateToBlockListScreen() } @OptIn(ExperimentalMaterial3Api::class) @@ -42,8 +43,10 @@ fun SettingScreen( navigateUp = { navigator.navigateUp() }, action = { Icon( - modifier = Modifier.clickable { navigator.navigateToNotificationSetting() }, - imageVector = ImageVector.vectorResource(R.drawable.setting), + modifier = Modifier + .padding(end = 16.dp) + .clickable { navigator.navigateToNotificationSetting() }, + imageVector = ImageVector.vectorResource(R.drawable.ic_setting), contentDescription = stringResource(R.string.setting_icon), tint = WeSpotThemeManager.colors.secondaryBtnColor, ) @@ -83,6 +86,7 @@ fun SettingScreen( ) EntireListItem(text = stringResource(R.string.block_list)) { + navigator.navigateToBlockListScreen() } EntireListItem(text = stringResource(R.string.account_setting)) { diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/revoke/RevokeConfirmScreen.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/revoke/RevokeConfirmScreen.kt index a754b5f0..d18421fa 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/revoke/RevokeConfirmScreen.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/screen/revoke/RevokeConfirmScreen.kt @@ -38,6 +38,7 @@ import com.bff.wespot.entire.R import com.bff.wespot.entire.screen.state.EntireAction import com.bff.wespot.entire.screen.state.EntireSideEffect import com.bff.wespot.entire.screen.viewmodel.EntireViewModel +import com.bff.wespot.navigation.Navigator import com.bff.wespot.navigation.util.EXTRA_TOAST_MESSAGE import com.bff.wespot.ui.WSBottomSheet import com.ramcosta.composedestinations.annotation.Destination @@ -54,6 +55,7 @@ interface RevokeConfirmNavigator { @Composable fun RevokeConfirmScreen( navigator: RevokeConfirmNavigator, + activityNavigator: Navigator, viewModel: EntireViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -66,7 +68,7 @@ fun RevokeConfirmScreen( viewModel.collectSideEffect { when (it) { is EntireSideEffect.NavigateToAuth -> { - val intent = it.navigator.navigateToAuth(context) + val intent = activityNavigator.navigateToAuth(context) intent.putExtra(EXTRA_TOAST_MESSAGE, context.getString(R.string.revoke_done)) context.startActivity(intent) } @@ -144,7 +146,7 @@ fun RevokeConfirmScreen( cancelButtonClick = { action(EntireAction.OnRevokeButtonClicked) }, - onDismissRequest = { showDialog = false }, + onDismissRequest = { }, ) } } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireAction.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireAction.kt index f17376dc..d136f602 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireAction.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireAction.kt @@ -3,8 +3,11 @@ package com.bff.wespot.entire.screen.state sealed class EntireAction { data object OnEntireScreenEntered : EntireAction() data object OnRevokeScreenEntered : EntireAction() + data object OnBlockListScreenEntered : EntireAction() data object OnRevokeConfirmed : EntireAction() data object OnSignOutButtonClicked : EntireAction() data object OnRevokeButtonClicked : EntireAction() + data object UnBlockMessage : EntireAction() + data class OnUnBlockButtonClicked(val messageId: Int) : EntireAction() data class OnRevokeReasonSelected(val reason: String) : EntireAction() } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireSideEffect.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireSideEffect.kt index c54521de..521ee8d4 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireSideEffect.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireSideEffect.kt @@ -1,7 +1,5 @@ package com.bff.wespot.entire.screen.state -import com.bff.wespot.navigation.Navigator - sealed class EntireSideEffect { - data class NavigateToAuth(val navigator: Navigator) : EntireSideEffect() + data object NavigateToAuth : EntireSideEffect() } diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireUiState.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireUiState.kt index dd6ffb5c..ee91a8a5 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireUiState.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/state/EntireUiState.kt @@ -1,9 +1,14 @@ package com.bff.wespot.entire.screen.state +import com.bff.wespot.model.message.response.BlockedMessage import com.bff.wespot.model.user.response.Profile data class EntireUiState( val profile: Profile = Profile(), val revokeReasonList: List = listOf(), val revokeConfirmed: Boolean = false, + val blockedMessageList: List = listOf(), + val unBlockList: List = listOf(), + val unBlockMessageId: Int = -1, + val isLoading: Boolean = false, ) diff --git a/feature/entire/src/main/java/com/bff/wespot/entire/screen/viewmodel/EntireViewModel.kt b/feature/entire/src/main/java/com/bff/wespot/entire/screen/viewmodel/EntireViewModel.kt index 18b5dc5b..f5ee7c7b 100644 --- a/feature/entire/src/main/java/com/bff/wespot/entire/screen/viewmodel/EntireViewModel.kt +++ b/feature/entire/src/main/java/com/bff/wespot/entire/screen/viewmodel/EntireViewModel.kt @@ -3,11 +3,12 @@ package com.bff.wespot.entire.screen.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bff.wespot.domain.repository.auth.AuthRepository +import com.bff.wespot.domain.repository.message.MessageRepository +import com.bff.wespot.domain.repository.message.MessageStorageRepository import com.bff.wespot.domain.repository.user.UserRepository import com.bff.wespot.entire.screen.state.EntireAction import com.bff.wespot.entire.screen.state.EntireSideEffect import com.bff.wespot.entire.screen.state.EntireUiState -import com.bff.wespot.navigation.Navigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import org.orbitmvi.orbit.ContainerHost @@ -22,7 +23,8 @@ import javax.inject.Inject class EntireViewModel @Inject constructor( private val userRepository: UserRepository, private val authRepository: AuthRepository, - private val navigator: Navigator, + private val messageRepository: MessageRepository, + private val messageStorageRepository: MessageStorageRepository, ) : ViewModel(), ContainerHost { override val container = container(EntireUiState()) @@ -32,6 +34,9 @@ class EntireViewModel @Inject constructor( EntireAction.OnRevokeButtonClicked -> revokeUser() EntireAction.OnSignOutButtonClicked -> signOut() EntireAction.OnRevokeConfirmed -> handleRevokeConfirmed() + EntireAction.OnBlockListScreenEntered -> getUnBlockedMessage() + EntireAction.UnBlockMessage -> unblockMessage() + is EntireAction.OnUnBlockButtonClicked -> handleUnBlockButtonClicked(action.messageId) is EntireAction.OnRevokeReasonSelected -> handleRevokeReasonSelected(action.reason) } } @@ -53,7 +58,7 @@ class EntireViewModel @Inject constructor( // TODO Token 삭제 authRepository.revoke(state.revokeReasonList) .onSuccess { - postSideEffect(EntireSideEffect.NavigateToAuth(navigator)) + postSideEffect(EntireSideEffect.NavigateToAuth) } .onFailure { Timber.e(it) @@ -64,7 +69,43 @@ class EntireViewModel @Inject constructor( private fun signOut() = intent { viewModelScope.launch { // TODO Token 삭제 - postSideEffect(EntireSideEffect.NavigateToAuth(navigator)) + postSideEffect(EntireSideEffect.NavigateToAuth) + } + } + + private fun getUnBlockedMessage() = intent { + viewModelScope.launch { + messageRepository.getBlockedMessage(cursorId = 0) // TODO Cursor Paging + .onSuccess { blockedMessageList -> + reduce { state.copy(blockedMessageList = blockedMessageList) } + } + .onFailure { + Timber.e(it) + } + } + } + + private fun handleUnBlockButtonClicked(messageId: Int) = intent { + reduce { state.copy(unBlockMessageId = messageId) } + } + + private fun unblockMessage() = intent { + reduce { state.copy(isLoading = true) } + + viewModelScope.launch { + messageStorageRepository.blockMessage(state.unBlockMessageId) + .onSuccess { + if (state.unBlockList.contains(state.unBlockMessageId).not()) { + val updatedList = state.unBlockList.toMutableList().apply { + add(state.unBlockMessageId) + } + reduce { state.copy(unBlockList = updatedList, isLoading = false) } + } + } + .onFailure { + reduce { state.copy(isLoading = false) } + Timber.e(it) + } } } diff --git a/feature/entire/src/main/res/drawable/setting.xml b/feature/entire/src/main/res/drawable/ic_setting.xml similarity index 100% rename from feature/entire/src/main/res/drawable/setting.xml rename to feature/entire/src/main/res/drawable/ic_setting.xml diff --git a/feature/entire/src/main/res/values/strings.xml b/feature/entire/src/main/res/values/strings.xml index 8a94a48c..6e19b471 100644 --- a/feature/entire/src/main/res/values/strings.xml +++ b/feature/entire/src/main/res/values/strings.xml @@ -48,4 +48,7 @@ " • 탈퇴 후 계정을 복구하고 싶다면 15일(360시간)이\n 지나기 전에 가입한 계정으로 다시 로그인해 주세요" 모든 내용을 숙지하였고, 탈퇴에 동의합니다 탈퇴하기 + 차단 해제 + 해제 완료 + 차단 해제하시나요? \ No newline at end of file diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/ReservedMessageScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/ReservedMessageScreen.kt index 396f2a9a..d3ce58c0 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/ReservedMessageScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/ReservedMessageScreen.kt @@ -1,22 +1,14 @@ package com.bff.wespot.message.screen -import android.graphics.Color.parseColor -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FilterChip -import androidx.compose.material3.FilterChipDefaults -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -27,14 +19,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import coil.compose.AsyncImage -import coil.request.ImageRequest import com.bff.wespot.designsystem.R.string import com.bff.wespot.designsystem.component.header.WSTopBar import com.bff.wespot.designsystem.component.indicator.WSToast @@ -47,7 +34,7 @@ import com.bff.wespot.message.state.MessageAction import com.bff.wespot.message.state.send.SendAction import com.bff.wespot.message.viewmodel.MessageViewModel import com.bff.wespot.message.viewmodel.SendViewModel -import com.bff.wespot.model.message.response.Message +import com.bff.wespot.ui.ReservedMessageItem import com.ramcosta.composedestinations.annotation.Destination import org.orbitmvi.orbit.compose.collectAsState @@ -104,7 +91,11 @@ fun ReservedMessageScreen( ) { items(state.reservedMessageList, key = { message -> message.id }) { item -> ReservedMessageItem( - reservedMessage = item, + title = stringResource(string.letter_receiver), + subTitle = item.receiver.toDescription(), + backgroundColor = item.receiver.profileCharacter.backgroundColor, + iconUrl = item.receiver.profileCharacter.iconUrl, + chipText = stringResource(R.string.message_edit), onClick = { navigator.navigateMessageEditScreen( args = EditMessageScreenArgs(true, item.id), @@ -138,81 +129,3 @@ fun ReservedMessageScreen( showToast = navArgs.isMessageEdit } } - -@Composable -fun ReservedMessageItem( - reservedMessage: Message, - onClick: () -> Unit, -) { - Column { - Row( - modifier = Modifier.padding(start = 18.dp, end = 18.dp, top = 12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - val profile = reservedMessage.receiver.profileCharacter - - Box( - modifier = Modifier - .size(42.dp) - .clip(CircleShape) - .background( - runCatching { - Color(parseColor(profile.backgroundColor)) - }.getOrDefault(WeSpotThemeManager.colors.cardBackgroundColor), - ), - contentAlignment = Alignment.Center, - ) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(profile.iconUrl) - .crossfade(true) - .build(), - contentDescription = stringResource(com.bff.wespot.ui.R.string.user_character_image), - ) - } - - Column( - modifier = Modifier - .padding(horizontal = 10.dp) - .weight(1f), - ) { - Text( - text = stringResource(id = string.letter_receiver), - style = StaticTypeScale.Default.body6, - color = WeSpotThemeManager.colors.txtSubColor, - ) - - Text( - text = reservedMessage.receiver.toDescription(), - style = StaticTypeScale.Default.body6, - color = WeSpotThemeManager.colors.txtTitleColor, - ) - } - - FilterChip( - shape = WeSpotThemeManager.shapes.extraLarge, - onClick = { - onClick() - }, - selected = false, - label = { - Text( - text = stringResource(R.string.message_edit), - style = StaticTypeScale.Default.body6, - ) - }, - border = null, - colors = FilterChipDefaults.filterChipColors( - containerColor = WeSpotThemeManager.colors.secondaryBtnColor, - labelColor = Color(0xFFF7F7F8), - ), - ) - } - - HorizontalDivider( - modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp), - thickness = 1.dp, - color = WeSpotThemeManager.colors.cardBackgroundColor, - ) - } -} diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt index 4121bb1a..b13d556e 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageEditScreen.kt @@ -229,7 +229,7 @@ fun MessageEditScreen( navigator.navigateMessageScreen(args = MessageScreenArgs(false)) }, cancelButtonClick = { exitDialog = false }, - onDismissRequest = { exitDialog = false }, + onDismissRequest = { }, ) } @@ -241,7 +241,7 @@ fun MessageEditScreen( cancelButtonText = stringResource(R.string.cancel), okButtonClick = { action(SendAction.OnSendButtonClicked) }, cancelButtonClick = { reserveDialog = false }, - onDismissRequest = { reserveDialog = false }, + onDismissRequest = { }, ) } @@ -255,7 +255,7 @@ fun MessageEditScreen( navigator.navigateMessageScreen(args = MessageScreenArgs(false)) }, cancelButtonClick = { timeoutDialog = false }, - onDismissRequest = { timeoutDialog = false }, + onDismissRequest = { }, ) } } diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt index 1341aa81..92e00cd5 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/MessageWriteScreen.kt @@ -176,7 +176,7 @@ fun MessageWriteScreen( navigator.navigateMessageScreen(args = MessageScreenArgs(false)) }, cancelButtonClick = { dialogState = false }, - onDismissRequest = { dialogState = false }, + onDismissRequest = { }, ) } diff --git a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt index 1c9358fc..2f22dab7 100644 --- a/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt +++ b/feature/message/src/main/kotlin/com/bff/wespot/message/screen/send/ReceiverSelectionScreen.kt @@ -229,7 +229,7 @@ fun ReceiverSelectionScreen( navigator.navigateMessageScreen(args = MessageScreenArgs(false)) }, cancelButtonClick = { dialogState = false }, - onDismissRequest = { dialogState = false }, + onDismissRequest = { }, ) }