Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
85377bb
[feat]: getOthersFollowers 추가(#73)
JJUYAAA Aug 13, 2025
a4f10c5
[feat]: getUserFollowers 추가(#73)
JJUYAAA Aug 13, 2025
fb30a12
[feat]: 다른 사용자 팔로워 목록 조회 뷰모델 생성(#73)
JJUYAAA Aug 13, 2025
3f1bfa5
[feat]: 다른 사용자 팔로워 목록 조회 화면 생성 및 content screen 분리(#73)
JJUYAAA Aug 13, 2025
eccfb14
[feat]: 마이페이지 정보 응답 dto(#73)
JJUYAAA Aug 13, 2025
09795c5
[feat]: getMyPage 서비스 메서드 생성(#73)
JJUYAAA Aug 13, 2025
8ad2f23
[feat]: 레포지토리에 getMyPageInfo 생성(#73)
JJUYAAA Aug 13, 2025
53e6a6a
[feat]: 마이페이지 뷰모델 생성(#73)
JJUYAAA Aug 14, 2025
57071ea
[feat]: 마이페이지 screen content 분리 및 뷰모델 적용(#73)
JJUYAAA Aug 14, 2025
0a2140c
[feat]: 마이페이지 navigation 확장함수 추가(#73)
JJUYAAA Aug 14, 2025
91d1c2f
[feat]: 마이페이지 navigation 적용(#73)
JJUYAAA Aug 14, 2025
2a5fdff
[ui]: CardBookList 수정 - 북마크 아이콘 다시 보이도록 (#73)
JJUYAAA Aug 14, 2025
3411239
[ui]: bookContent 수정 - padding값 다시 조정 (#73)
JJUYAAA Aug 14, 2025
76eabb9
[ui]: FeedContent 수정 - padding값 다시 조정 (#73)
JJUYAAA Aug 14, 2025
bacbc40
[refactor]: FeedScreen-> 안쓰는 profileImage 변수 삭제 (#73)
JJUYAAA Aug 14, 2025
2c0394f
[refactor]: SavedFeedCard-> 안쓰는 profileImage 변수 삭제 (#73)
JJUYAAA Aug 14, 2025
86ab8e5
[ui]: 마이페이지 저장 스크린 - content 영역에 weight 적용 (#73)
JJUYAAA Aug 14, 2025
4e8db9d
[feat]: 닉네임 중복검증을 위한 요청, 응답 dto 생성 (#73)
JJUYAAA Aug 14, 2025
4814787
[feat]: 닉네임 중복검증을 위한 service, repository 메서드 생성 (#73)
JJUYAAA Aug 14, 2025
ddd4bf9
[feat]: 닉네임 뷰모델 생성 (#73)
JJUYAAA Aug 14, 2025
122c4d5
[feat]: 닉네임 screen, content 분리 및 뷰모델 적용 (#73)
JJUYAAA Aug 14, 2025
4801c34
[refactor]: users dto 패키지 정리(#73)
JJUYAAA Aug 14, 2025
0cc22ee
[refactor]: users dto 패키지 정리(#73)
JJUYAAA Aug 14, 2025
24f93c5
[feat]: 장르선택 응답dto 생성 (#73)
JJUYAAA Aug 14, 2025
1f236b7
[feat]: roleitem 이미지, 컬러 타입 변경 (#73)
JJUYAAA Aug 14, 2025
5be875d
[feat]: roleitem 이미지, 컬러 타입 변경 (#73)
JJUYAAA Aug 14, 2025
bd1884a
[feat]: 장르선택 뷰모델 생성 (#73)
JJUYAAA Aug 14, 2025
2667493
[feat]: 장르선택 screen, content 분리 및 뷰모델 적용 (#73)
JJUYAAA Aug 14, 2025
a94d14d
[feat]: 내 팔로잉 최근 피드 작성자순으로 - 응답 dto 생성 (#73)
JJUYAAA Aug 14, 2025
043a06d
[feat]: 내 팔로잉 최근 피드 작성자순으로 - 유저서비스, 레포지토리 수정 (#73)
JJUYAAA Aug 14, 2025
48addb7
[feat]: 내 팔로잉 최근 피드 작성자순으로 - FeedScreen 수정 (#73)
JJUYAAA Aug 14, 2025
1b40933
[feat]: Feed 뷰모델 생성 (#73)
JJUYAAA Aug 14, 2025
d6de6da
[feat]: 팔로잉 상태 변경 - 요청, 응답 dto 생성 (#73)
JJUYAAA Aug 14, 2025
ab12abc
[feat]: 팔로잉 상태 변경 - 유저 레포지토리, 서비스 수정 (#73)
JJUYAAA Aug 14, 2025
ab8093b
[feat]: 내 구독 뷰모델 수정 - 토글+팔로잉 상태 변경 적용 (#73)
JJUYAAA Aug 14, 2025
02e93c9
Merge branch 'develop' into feat/#73-users_api
Nico1eKim Aug 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.texthip.thip.data.model.users.request

import kotlinx.serialization.Serializable

@Serializable
data class FollowRequest(
val type: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.texthip.thip.data.model.users.request

import kotlinx.serialization.Serializable

@Serializable
data class NicknameRequest(
val nickname: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.texthip.thip.data.model.users.response

import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable

@Serializable
data class AliasChoiceResponse(
@SerializedName("aliasChoices") val aliasChoices: List<AliasChoice>
)

@Serializable
data class AliasChoice(
@SerializedName("aliasName") val aliasName: String,
@SerializedName("categoryName") val categoryName: String,
@SerializedName("imageUrl") val imageUrl: String,
@SerializedName("aliasColor") val aliasColor: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.texthip.thip.data.model.users.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class FollowResponse(
@SerialName("isFollowing") val isFollowing: Boolean
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.texthip.thip.data.model.users
package com.texthip.thip.data.model.users.response

import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
Expand All @@ -13,10 +13,22 @@ data class MyFollowingsResponse(

@Serializable
data class FollowingList(
@SerializedName("userId") val userId: Int,
@SerializedName("userId") val userId: Long,
@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
)

@Serializable
data class MyRecentFollowingsResponse(
@SerializedName("recentWriters") val recentWriters: List<RecentWriterList>
)

@Serializable
data class RecentWriterList(
@SerializedName("userId") val userId: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImageUrl") val profileImageUrl: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.texthip.thip.data.model.users.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class MyPageInfoResponse(
@SerialName("profileImageUrl") val profileImageUrl: String?,
@SerialName("nickname") val nickname: String,
@SerialName("aliasName") val aliasName: String,
@SerialName("aliasColor") val aliasColor: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.texthip.thip.data.model.users.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class NicknameResponse(
@SerialName("isVerified") val isVerified: Boolean
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
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.model.users.request.FollowRequest
import com.texthip.thip.data.model.users.response.MyFollowingsResponse
import com.texthip.thip.data.model.users.response.MyPageInfoResponse
import com.texthip.thip.data.model.users.request.NicknameRequest
import com.texthip.thip.data.model.users.response.AliasChoiceResponse
import com.texthip.thip.data.model.users.response.FollowResponse
import com.texthip.thip.data.model.users.response.MyRecentFollowingsResponse
import com.texthip.thip.data.model.users.response.NicknameResponse
import com.texthip.thip.data.model.users.response.OthersFollowersResponse
import com.texthip.thip.data.service.UserService
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserRepository@Inject constructor(
class UserRepository @Inject constructor(
private val userService: UserService
) {
//내 팔로잉 목록 조회
suspend fun getMyFollowings(
cursor: String?,
size: Int = 10
Expand All @@ -18,4 +27,50 @@ class UserRepository@Inject constructor(
.handleBaseResponse()
.getOrThrow()
}

suspend fun getRecentWriters(): Result<MyRecentFollowingsResponse?> = runCatching {
userService.getRecentWriters()
.handleBaseResponse()
.getOrThrow()
}

//다른 유저 팔로워 목록 조회
suspend fun getOthersFollowers(
userId: Long,
cursor: String?,
size: Int = 10
): Result<OthersFollowersResponse?> = runCatching {
userService.getUserFollowers(userId = userId, cursor = cursor, size = size)
.handleBaseResponse()
.getOrThrow()
}

//마이페이지 정보 조회
suspend fun getMyPageInfo(): Result<MyPageInfoResponse?> = runCatching {
userService.getMyPage()
.handleBaseResponse()
.getOrThrow()
}

suspend fun checkNickname(nickname: String): Result<NicknameResponse?> = runCatching {
userService.checkNickname(NicknameRequest(nickname))
.handleBaseResponse()
.getOrThrow()
}

suspend fun getAliasChoices(): Result<AliasChoiceResponse?> = runCatching {
userService.getAliasChoices()
.handleBaseResponse()
.getOrThrow()
}

suspend fun toggleFollow(
followingUserId: Long,
isFollowing: Boolean
): Result<FollowResponse?> = runCatching {
val request = FollowRequest(type = isFollowing)
userService.toggleFollow(followingUserId, request)
.handleBaseResponse()
.getOrThrow()
}
}
39 changes: 37 additions & 2 deletions app/src/main/java/com/texthip/thip/data/service/UserService.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
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 com.texthip.thip.data.model.users.request.FollowRequest
import com.texthip.thip.data.model.users.response.MyFollowingsResponse
import com.texthip.thip.data.model.users.response.MyPageInfoResponse
import com.texthip.thip.data.model.users.request.NicknameRequest
import com.texthip.thip.data.model.users.response.AliasChoiceResponse
import com.texthip.thip.data.model.users.response.FollowResponse
import com.texthip.thip.data.model.users.response.MyRecentFollowingsResponse
import com.texthip.thip.data.model.users.response.NicknameResponse
import com.texthip.thip.data.model.users.response.OthersFollowersResponse
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query

Expand All @@ -14,4 +23,30 @@ interface UserService {
@Query("cursor") cursor: String? = null
): BaseResponse<MyFollowingsResponse>

@GET("users/my-followings/recent-feeds")
suspend fun getRecentWriters(): BaseResponse<MyRecentFollowingsResponse>

@GET("users/{userId}/followers")
suspend fun getUserFollowers(
@Path("userId") userId: Long,
@Query("size") size: Int = 10,
@Query("cursor") cursor: String? = null
): BaseResponse<OthersFollowersResponse>

@GET("users/my-page")
suspend fun getMyPage(): BaseResponse<MyPageInfoResponse>

@POST("users/nickname")
suspend fun checkNickname(
@Body request: NicknameRequest
): BaseResponse<NicknameResponse>

@GET("users/alias")
suspend fun getAliasChoices(): BaseResponse<AliasChoiceResponse>

@POST("users/following/{followingUserId}")
suspend fun toggleFollow(
@Path("followingUserId") followingUserId: Long,
@Body request: FollowRequest
): BaseResponse<FollowResponse>
}
33 changes: 21 additions & 12 deletions app/src/main/java/com/texthip/thip/ui/common/cards/CardBookList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand All @@ -18,7 +20,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand All @@ -34,6 +38,7 @@ fun CardBookList(
author: String,
publisher: String,
imageUrl: String? = null, // API에서 받은 이미지 URL
showBookmark: Boolean = false,
isBookmarked: Boolean = false,
onBookmarkClick: () -> Unit = {}
) {
Expand Down Expand Up @@ -74,17 +79,20 @@ fun CardBookList(

Spacer(modifier = Modifier.width(12.dp))

// 북마크 아이콘 제거(쓰는 화면이 안보임)
/*IconButton(
onClick = onBookmarkClick,
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = if (isBookmarked) ImageVector.vectorResource(R.drawable.ic_save_filled) else ImageVector.vectorResource(R.drawable.ic_save),
contentDescription = "북마크",
tint = if (isBookmarked) colors.Purple else colors.Grey01
)
}*/
if(showBookmark) {
IconButton(
onClick = onBookmarkClick,
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = if (isBookmarked) ImageVector.vectorResource(R.drawable.ic_save_filled) else ImageVector.vectorResource(
R.drawable.ic_save
),
contentDescription = "북마크",
tint = if (isBookmarked) colors.Purple else colors.Grey01
)
}
}
}
}

Expand All @@ -93,6 +101,7 @@ fun CardBookList(
@Composable
fun PreviewBookTitleCard() {
var isBookmarked by remember { mutableStateOf(false) }
var showBookmark by remember { mutableStateOf(true) }

Column(
modifier = Modifier.padding(16.dp),
Expand All @@ -102,9 +111,9 @@ fun PreviewBookTitleCard() {
title = "책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.책제목입니다.",
author = "리처드 도킨스",
publisher = "을유문화사",
showBookmark = showBookmark,
isBookmarked = isBookmarked,
onBookmarkClick = { isBookmarked = !isBookmarked }
)
}

}
13 changes: 7 additions & 6 deletions app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ 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.feed.viewmodel.FeedViewModel
import com.texthip.thip.ui.mypage.component.SavedFeedCard
import com.texthip.thip.ui.mypage.mock.FeedItem
import com.texthip.thip.ui.theme.ThipTheme
Expand All @@ -67,10 +67,12 @@ fun FeedScreen(
totalFeedCount: Int = 0,
selectedTabIndex: Int = 0,
followerProfileImageUrls: List<String> = emptyList(),
feedViewModel: FeedViewModel = hiltViewModel()
resultFeedId: Int? = null,
onResultConsumed: () -> Unit = {},
viewModel: MySubscriptionViewModel = hiltViewModel()
mySubscriptionViewModel: MySubscriptionViewModel = hiltViewModel()
) {
val feedUiState by feedViewModel.uiState.collectAsState()
val selectedIndex = rememberSaveable { mutableIntStateOf(selectedTabIndex) }
val feedStateList = remember {
mutableStateListOf<FeedItem>().apply {
Expand Down Expand Up @@ -288,14 +290,14 @@ fun FeedScreen(
//피드
item {
Spacer(modifier = Modifier.height(20.dp))
val subscriptionsForBar = subscriptionUiState.followings.map { user ->
val subscriptionsForBar = feedUiState.recentWriters.map { user ->
MySubscriptionData(
profileImageUrl = user.profileImageUrl,
nickname = user.nickname,
role = user.aliasName,
role = "",
roleColor = colors.White,
subscriberCount = 0,
isSubscribed = user.isFollowing
isSubscribed = true
)
}
MySubscribeBarlist(
Expand All @@ -309,7 +311,6 @@ fun FeedScreen(

SavedFeedCard(
feedItem = feed,
profileImage = profileImage,
onBookmarkClick = {
val updated = feed.copy(isSaved = !feed.isSaved)
feedStateList[index] = updated
Expand Down
Loading