diff --git a/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt b/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt index bf7eafad..eb8dd0fd 100644 --- a/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt +++ b/app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt @@ -3,6 +3,7 @@ package com.texthip.thip.data.di import com.texthip.thip.data.service.BookService import com.texthip.thip.data.service.GroupService import com.texthip.thip.data.service.RoomsService +import com.texthip.thip.data.service.UserService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -29,4 +30,10 @@ object ServiceModule { @Singleton fun providesRoomsService(retrofit: Retrofit): RoomsService = retrofit.create(RoomsService::class.java) + + @Provides + @Singleton + fun provideUserService(retrofit: Retrofit): UserService { + return retrofit.create(UserService::class.java) + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt b/app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt new file mode 100644 index 00000000..91807af3 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/users/MyFollowingsResponse.kt @@ -0,0 +1,22 @@ +package com.texthip.thip.data.model.users + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class MyFollowingsResponse( + @SerializedName("followings") val followings: List, + @SerializedName("totalFollowingCount") val totalFollowingCount: Int, + @SerializedName("nextCursor") val nextCursor: String?, + @SerializedName("isLast") val isLast: Boolean +) + +@Serializable +data class FollowingList( + @SerializedName("userId") val userId: Int, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String?, + @SerializedName("aliasName") val aliasName: String, + @SerializedName("aliasColor") val aliasColor: String, + @SerializedName("isFollowing") val isFollowing: Boolean +) diff --git a/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt new file mode 100644 index 00000000..e048f8cb --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt @@ -0,0 +1,21 @@ +package com.texthip.thip.data.repository + +import com.texthip.thip.data.model.base.handleBaseResponse +import com.texthip.thip.data.model.users.MyFollowingsResponse +import com.texthip.thip.data.service.UserService +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class UserRepository@Inject constructor( + private val userService: UserService +) { + suspend fun getMyFollowings( + cursor: String?, + size: Int = 10 + ): Result = runCatching { + userService.getMyFollowings(cursor = cursor, size = size) + .handleBaseResponse() + .getOrThrow() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/service/UserService.kt b/app/src/main/java/com/texthip/thip/data/service/UserService.kt new file mode 100644 index 00000000..b2c1f429 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/service/UserService.kt @@ -0,0 +1,17 @@ +package com.texthip.thip.data.service + +import com.texthip.thip.data.model.base.BaseResponse +import com.texthip.thip.data.model.rooms.response.RoomsUsersResponse +import com.texthip.thip.data.model.users.MyFollowingsResponse +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface UserService { + @GET("users/my-followings") + suspend fun getMyFollowings( + @Query("size") size: Int = 10, + @Query("cursor") cursor: String? = null + ): BaseResponse + +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt b/app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt index 36cef65b..94b4c152 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/header/AuthorHeader.kt @@ -29,6 +29,7 @@ import com.texthip.thip.ui.common.buttons.OutlinedButton import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import androidx.compose.ui.graphics.Color @Composable fun AuthorHeader( @@ -36,6 +37,7 @@ fun AuthorHeader( profileImage: String?, nickname: String, badgeText: String, + badgeTextColor: Color = colors.NeonGreen, buttonText: String = "", buttonWidth: Dp = 60.dp, showButton: Boolean = true, @@ -80,7 +82,7 @@ fun AuthorHeader( Text( text = badgeText, style = typography.feedcopy_r400_s14_h20, - color = colors.NeonGreen, + color = badgeTextColor, maxLines = 1 ) } @@ -131,6 +133,7 @@ fun PreviewAuthorHeader() { profileImage = null, nickname = "열자자제한열열자제한", badgeText = "칭호칭호칭호", + badgeTextColor = colors.Yellow, showButton = false, showThipNum = true, thipNum = 10, diff --git a/app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt b/app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt index ca883a4e..eea3a257 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/component/MySubscribelistBar.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -105,7 +106,8 @@ fun MySubscribeBarlist( overflow = TextOverflow.Ellipsis, style = typography.view_r400_s11_h20, color = colors.White, - modifier = Modifier.width(36.dp) + modifier = Modifier.width(36.dp), + textAlign = TextAlign.Center ) } Spacer(modifier = Modifier.width(12.dp)) @@ -160,7 +162,7 @@ private fun MySubscribeBarlistPrev() { val previewData = List(10) { MySubscriptionData( profileImageUrl = "https://example.com/profile$it.jpg", - nickname = "닉네임$it", + nickname = "닉네임임$it", role = "문학가", roleColor = colors.Red, subscriberCount = 100 + it diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt index 10831a8c..45220924 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt @@ -13,16 +13,19 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.texthip.thip.R import com.texthip.thip.ui.common.buttons.FloatingButton @@ -33,21 +36,26 @@ import com.texthip.thip.ui.feed.component.FeedSubscribeBarlist import com.texthip.thip.ui.feed.component.MyFeedCard import com.texthip.thip.ui.feed.component.MySubscribeBarlist import com.texthip.thip.ui.feed.mock.MySubscriptionData +import com.texthip.thip.ui.feed.viewmodel.MySubscriptionViewModel import com.texthip.thip.ui.mypage.component.SavedFeedCard import com.texthip.thip.ui.mypage.mock.FeedItem +import com.texthip.thip.ui.navigator.routes.FeedRoutes import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography + @Composable fun FeedScreen( navController: NavController? = null, + onNavigateToMySubscription: () -> Unit = {}, nickname: String = "", userRole: String = "", feeds: List = emptyList(), totalFeedCount: Int = 0, selectedTabIndex: Int = 0, - followerProfileImageUrls: List = emptyList() + followerProfileImageUrls: List = emptyList(), + viewModel: MySubscriptionViewModel = hiltViewModel() ) { val selectedIndex = rememberSaveable { mutableIntStateOf(selectedTabIndex) } val feedStateList = remember { @@ -105,6 +113,7 @@ fun FeedScreen( roleColor = colors.SocialScience ) ) + val subscriptionUiState by viewModel.uiState.collectAsState() Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.fillMaxSize() @@ -121,7 +130,8 @@ fun FeedScreen( selectedTabIndex = selectedIndex.value, onTabSelected = { selectedIndex.value = it } ) - // 스크롤 영역 + + // 스크롤 영역 전체 LazyColumn( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp) @@ -203,11 +213,20 @@ fun FeedScreen( //피드 item { Spacer(modifier = Modifier.height(20.dp)) + val subscriptionsForBar = subscriptionUiState.followings.map { user -> + MySubscriptionData( + profileImageUrl = user.profileImageUrl, + nickname = user.nickname, + role = user.aliasName, + roleColor = colors.White, + subscriberCount = 0, + isSubscribed = user.isFollowing + ) + } MySubscribeBarlist( modifier = Modifier.padding(horizontal = 20.dp), - subscriptions = mySubscriptions, - onClick = { - } + subscriptions = subscriptionsForBar, + onClick = onNavigateToMySubscription ) } itemsIndexed(feedStateList, key = { _, item -> item.id }) { index, feed -> @@ -300,7 +319,7 @@ private fun FeedScreenWithoutDataPreview() { FeedScreen( nickname = "ThipUser01", userRole = "문학 칭호", - selectedTabIndex = 1, + selectedTabIndex = 0, feeds = mockFeeds, totalFeedCount = mockFeeds.size, followerProfileImageUrls = mockFollowerImages diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt index 1eed1583..e3404c90 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/MySubscriptionListScreen.kt @@ -3,21 +3,27 @@ package com.texthip.thip.ui.feed.screen import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Text +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -25,152 +31,101 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.texthip.thip.R +import com.texthip.thip.data.model.users.FollowingList +import com.texthip.thip.ui.common.header.AuthorHeader import com.texthip.thip.ui.common.modal.ToastWithDate import com.texthip.thip.ui.common.topappbar.DefaultTopAppBar -import com.texthip.thip.ui.feed.component.MySubscriptionList -import com.texthip.thip.ui.feed.mock.MySubscriptionData +import com.texthip.thip.ui.feed.viewmodel.MySubscriptionUiState +import com.texthip.thip.ui.feed.viewmodel.MySubscriptionViewModel import com.texthip.thip.ui.theme.ThipTheme import com.texthip.thip.ui.theme.ThipTheme.colors import com.texthip.thip.ui.theme.ThipTheme.typography +import com.texthip.thip.utils.color.hexToColor import kotlinx.coroutines.delay @Composable fun MySubscriptionScreen( - navController: NavController? = null, - titleText: String = stringResource(R.string.my_thip_list) + navController: NavController, + viewModel: MySubscriptionViewModel = hiltViewModel() ) { - val initialmembers = listOf( - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thipthip", - role = "공식 인플루언서", - roleColor = colors.NeonGreen, - subscriberCount = 50 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thip01", - role = "작가", - roleColor = colors.NeonGreen, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thipthip", - role = "공식 인플루언서", - roleColor = colors.NeonGreen, - subscriberCount = 50 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thip01", - role = "작가", - roleColor = colors.NeonGreen, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thipthip", - role = "공식 인플루언서", - roleColor = colors.NeonGreen, - subscriberCount = 50 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "Thiper", - role = "칭호칭호", - roleColor = colors.Yellow, - subscriberCount = 100 - ), - MySubscriptionData( - profileImageUrl = null, - nickname = "thip01", - role = "작가", - roleColor = colors.NeonGreen, - subscriberCount = 100 - ), - ) + val uiState by viewModel.uiState.collectAsState() + val lazyListState = rememberLazyListState() val context = LocalContext.current - var members by remember { mutableStateOf(initialmembers) } - var toastMessage by rememberSaveable { mutableStateOf(null) } + val isScrolledToEnd by remember { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + if (layoutInfo.totalItemsCount == 0) return@derivedStateOf false + val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 + lastVisibleItemIndex >= layoutInfo.totalItemsCount - 1 + } + } + + LaunchedEffect(isScrolledToEnd) { + if (isScrolledToEnd && !uiState.isLoading && !uiState.isLastPage) { + viewModel.fetchMyFollowings() + } + } - LaunchedEffect(toastMessage) { - if (toastMessage != null) { + MySubscriptionContent( + uiState = uiState, + lazyListState = lazyListState, + onNavigateBack = { navController.popBackStack() }, + onToggleFollow = { userId, nickname -> + val followedMessage = context.getString(R.string.toast_thip, nickname) + val unfollowedMessage = context.getString(R.string.toast_thip_cancel, nickname) + viewModel.toggleFollow(userId, followedMessage, unfollowedMessage) + }, + onHideToast = { viewModel.hideToast() } + ) +} +@Composable +fun MySubscriptionContent( + uiState: MySubscriptionUiState, + lazyListState: LazyListState, + onNavigateBack: () -> Unit, + onToggleFollow: (userId: Int, nickname: String) -> Unit, + onHideToast: () -> Unit +) { + LaunchedEffect(uiState.showToast) { + if (uiState.showToast) { delay(2000) - toastMessage = null + onHideToast() } } + Box(modifier = Modifier.fillMaxSize()) { - toastMessage?.let { message -> + if (uiState.showToast) { Box( modifier = Modifier .fillMaxWidth() .zIndex(1f) .align(Alignment.TopCenter) .padding(horizontal = 15.dp, vertical = 15.dp), - contentAlignment = Alignment.TopCenter ) { ToastWithDate( - message = message, + message = uiState.toastMessage, modifier = Modifier.fillMaxWidth() ) } } + Column( Modifier .background(colors.Black) .fillMaxSize() ) { DefaultTopAppBar( - onLeftClick = {}, - title = titleText + onLeftClick = onNavigateBack, + title = stringResource(R.string.my_thip_list) ) - Column( - modifier = Modifier - .fillMaxWidth() - - ) { + Column(modifier = Modifier.fillMaxWidth()) { Spacer(modifier = Modifier.height(40.dp)) Text( - text = stringResource(R.string.whole_num, members.size), + text = stringResource(R.string.whole_num, uiState.totalCount), style = typography.menu_m500_s14_h24, color = colors.Grey, modifier = Modifier @@ -182,37 +137,85 @@ fun MySubscriptionScreen( color = colors.DarkGrey02, thickness = 1.dp ) + LazyColumn( - modifier = Modifier - .fillMaxSize() + state = lazyListState, + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(vertical = 20.dp) ) { - item { - MySubscriptionList( - members = members, - onUnsubscribe = { nickname -> - members = members.map { - if (it.nickname == nickname) it.copy(isSubscribed = !it.isSubscribed) - else it - } - toastMessage = - if (members.find { it.nickname == nickname }?.isSubscribed == true) { - context.getString(R.string.toast_thip, nickname) - } else { - context.getString(R.string.toast_thip_cancel, nickname) - } + itemsIndexed( + items = uiState.followings, + key = { _, user -> user.userId } + ) { index, user -> + Column(modifier = Modifier.padding(horizontal = 20.dp)) { + AuthorHeader( + profileImage = user.profileImageUrl, + nickname = user.nickname, + badgeText = user.aliasName, + badgeTextColor = hexToColor(user.aliasColor), + buttonText = stringResource(if (user.isFollowing) R.string.thip_cancel else R.string.thip), + buttonWidth = 64.dp, + profileImageSize = 36.dp, + onButtonClick = { onToggleFollow(user.userId, user.nickname) } + ) + + if (index < uiState.followings.lastIndex) { + Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + color = colors.DarkGrey02, + thickness = 1.dp + ) + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + + if (uiState.isLoading && !uiState.isLastPage) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.weight(1f)) + CircularProgressIndicator(modifier = Modifier.size(24.dp)) + Spacer(modifier = Modifier.weight(1f)) } - ) + } } } } } } } - @Preview @Composable private fun MySubscriptionListScreenPrev() { + val mockUsers = (1..10).map { + FollowingList( + userId = it, + profileImageUrl = null, + nickname = "문학소년 $it", + aliasName = if (it % 3 == 0) "공식 인플루언서" else "글쓰는 탐험가", + aliasColor = if (it % 3 == 0) "#00C7B2" else "#FFD600", + isFollowing = true + ) + } + ThipTheme { - MySubscriptionScreen() + MySubscriptionContent( + uiState = MySubscriptionUiState( + isLoading = false, + followings = mockUsers, + totalCount = 25, + isLastPage = false, + showToast = false + ), + lazyListState = rememberLazyListState(), + onNavigateBack = {}, + onToggleFollow = { _, _ -> }, + onHideToast = {} + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt new file mode 100644 index 00000000..7f4f03c2 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/ui/feed/viewmodel/MySubscriptionViewModel.kt @@ -0,0 +1,96 @@ +package com.texthip.thip.ui.feed.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.texthip.thip.data.model.users.FollowingList +import com.texthip.thip.data.repository.UserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +data class MySubscriptionUiState( + val isLoading: Boolean = false, + val followings: List = emptyList(), + val totalCount: Int = 0, + val isLastPage: Boolean = false, + val errorMessage: String? = null, + val showToast: Boolean = false, + val toastMessage: String = "" +) + +@HiltViewModel +class MySubscriptionViewModel @Inject constructor( + private val userRepository: UserRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(MySubscriptionUiState()) + val uiState = _uiState.asStateFlow() + + private var nextCursor: String? = null + + init { + fetchMyFollowings(isInitial = true) + } + + fun fetchMyFollowings(isInitial: Boolean = false) { + if (_uiState.value.isLoading || (!isInitial && _uiState.value.isLastPage)) { + return + } + + viewModelScope.launch { + _uiState.update { it.copy(isLoading = true) } + val cursorToFetch = if (isInitial) null else nextCursor + + val result = userRepository.getMyFollowings(cursor = cursorToFetch) + + result.onSuccess { data -> + data?.let { + val newFollowings = it.followings + nextCursor = it.nextCursor + + _uiState.update { currentState -> + currentState.copy( + isLoading = false, + followings = if (isInitial) newFollowings else currentState.followings + newFollowings, + totalCount = it.totalFollowingCount, + isLastPage = it.isLast + ) + } + } ?: _uiState.update { + it.copy(isLoading = false, errorMessage = "응답 데이터가 비어있습니다.") + } + }.onFailure { exception -> + _uiState.update { + it.copy(isLoading = false, errorMessage = exception.message) + } + } + } + } + + + fun toggleFollow(userId: Int, followedMessage: String, unfollowedMessage: String) { + var toastMsg = "" + _uiState.update { currentState -> + val updatedList = currentState.followings.map { user -> + if (user.userId == userId) { + val isNowFollowing = !user.isFollowing + toastMsg = if (isNowFollowing) followedMessage else unfollowedMessage + user.copy(isFollowing = isNowFollowing) + } else { + user + } + } + currentState.copy( + followings = updatedList, + showToast = true, + toastMessage = toastMsg + ) + } + } + fun hideToast() { + _uiState.update { it.copy(showToast = false) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt index ebc035cc..ef71bb4e 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/extensions/FeedNavigationExtensions.kt @@ -1,9 +1,15 @@ package com.texthip.thip.ui.navigator.extensions import androidx.navigation.NavHostController +import com.texthip.thip.ui.navigator.routes.FeedRoutes import com.texthip.thip.ui.navigator.routes.MainTabRoutes // Feed 확장 함수 fun NavHostController.navigateToFeed() { navigate(MainTabRoutes.Feed) } + +// 내 띱 목록으로 +fun NavHostController.navigateToMySubscription() { + navigate(FeedRoutes.MySubscription) +} \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt index e7e3a8b9..740a9711 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/navigations/FeedNavigation.kt @@ -4,6 +4,9 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.texthip.thip.ui.feed.screen.FeedScreen +import com.texthip.thip.ui.feed.screen.MySubscriptionScreen +import com.texthip.thip.ui.navigator.extensions.navigateToMySubscription +import com.texthip.thip.ui.navigator.routes.FeedRoutes import com.texthip.thip.ui.navigator.routes.MainTabRoutes // Feed @@ -17,7 +20,13 @@ fun NavGraphBuilder.feedNavigation(navController: NavHostController) { feeds = emptyList(), totalFeedCount = 0, selectedTabIndex = 0, - followerProfileImageUrls = emptyList() + followerProfileImageUrls = emptyList(), + onNavigateToMySubscription = { + navController.navigateToMySubscription() + } ) } + composable { + MySubscriptionScreen(navController = navController) + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt b/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt index 6cc7bc71..4bb9935a 100644 --- a/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt +++ b/app/src/main/java/com/texthip/thip/ui/navigator/routes/FeedRoutes.kt @@ -7,4 +7,6 @@ sealed class FeedRoutes : Routes() { // 향후 추가될 Feed 관련 화면들 // @Serializable data object SubscriptionList : FeedRoutes // @Serializable data object Detail : FeedRoutes + + @Serializable data object MySubscription : FeedRoutes() } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt b/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt new file mode 100644 index 00000000..381ef36f --- /dev/null +++ b/app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt @@ -0,0 +1,12 @@ +package com.texthip.thip.utils.color + +import androidx.compose.ui.graphics.Color + +fun hexToColor(hex: String): Color { + return try { + Color(android.graphics.Color.parseColor(hex)) + } catch (e: IllegalArgumentException) { + //잘못된 형식이면 기본 색 + Color.White + } +} \ No newline at end of file