From 32f65bcb3ae72734e262470fa8ee994343acf60e Mon Sep 17 00:00:00 2001 From: yys Date: Fri, 13 Dec 2024 01:48:58 +0900 Subject: [PATCH 01/15] =?UTF-8?q?-=20=EC=9C=A0=EC=A0=80=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20(TODO.=20=EC=83=9D=EB=85=84?= =?UTF-8?q?=EC=9B=94=EC=9D=BC=20=EB=B0=9B=EC=95=84=EC=99=80=EC=84=9C=20?= =?UTF-8?q?=EB=82=98=EC=9D=B4=20=EA=B5=AC=ED=95=98=EA=B8=B0)=20-=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20GET=20=ED=95=A8=EC=88=98=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20Response=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yys #78 --- .../datasource/remote/response/UserResponse.kt | 15 ++++++++++++--- .../data/datasource/remote/service/UserService.kt | 9 +++++++++ .../eighteenandroid/data/mapper/UserMapper.kt | 5 ++--- .../data/repository/UserRepositoryImpl.kt | 5 +---- .../eighteen/eighteenandroid/domain/model/User.kt | 3 +-- .../presentation/home/MainFragment.kt | 14 ++++---------- .../presentation/teen/TeenListFragment.kt | 3 --- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt index 8c14ec2d..0ce245e2 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt @@ -1,10 +1,19 @@ package com.eighteen.eighteenandroid.data.datasource.remote.response +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class UserResponse( + @Json(name = "profileImage") val userImage: String, + @Json(name = "uniqueId") val userId: String, + @Json(name = "nickName") val name: String, - val age: String, - val userSchoolName: String, - val tag: String + @Json(name = "birthDate") + val birthDate: String, + val location: String, + val schoolName: String, + @Json(name = "likeStatus") + val isLike: Boolean ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt index 1b267ad1..002f3340 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt @@ -24,4 +24,13 @@ interface UserService { @POST("/v1/api/user/duplication-check") suspend fun postDuplicationCheck(@Query("uniqueId") uniqueId: String): Response> + + @POST("/v1/api/user/sign-in") + suspend fun postLogin(@Query("phoneNumber") phoneNumber: String): Response> + + @GET("/v1/api/{userType}/find-all") + suspend fun getAllUser(@Path("userType") userType: String, @Query("page") page: Int, @Query("size") size: Int): Response>> + + @GET("/v1/api/{userType}/find-all-by-category/{category}") + suspend fun getCategoryUser(@Path("userType") userType: String, @Path("category") category: String, @Query("page") page: Int, @Query("size") size: Int) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt index cf95a518..2065194c 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt @@ -10,9 +10,8 @@ object UserMapper { userImage = userImage, userId = userId, userName = name, - userAge = age, - userSchoolName = userSchoolName, - tag = tag + userAge = birthDate, /* TODO. 생년월일로 나이 구하기 */ + userSchoolName = schoolName ) } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt index a195d9bc..9bbb283d 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt @@ -34,8 +34,7 @@ class UserRepositoryImpl @Inject constructor(private val userService: UserServic userId = "1", userName = "김 에스더", userAge = "16", - userSchoolName = "서울 중학교", - tag = "운동" + userSchoolName = "서울 중학교" ), User( userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", @@ -43,7 +42,6 @@ class UserRepositoryImpl @Inject constructor(private val userService: UserServic userName = "김 에스더", userAge = "16", userSchoolName = "부천 중학교", - tag = "스터디" ), User( userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", @@ -51,7 +49,6 @@ class UserRepositoryImpl @Inject constructor(private val userService: UserServic userName = "김 에스더", userAge = "16", userSchoolName = "인천 중학교", - tag = "프로젝트" ) ) } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt index e6bdfa0c..15df968b 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt @@ -5,6 +5,5 @@ data class User( val userId: String, val userName: String, val userAge: String, - val userSchoolName: String, - val tag: String + val userSchoolName: String ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 76437875..b18b910f 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -395,8 +395,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userId = "1", userName = "김 에스더", userAge = "16", - userSchoolName = "서울 중학교", - tag = "운동" + userSchoolName = "서울 중학교" ) ), MainItem.UserView( @@ -406,7 +405,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userName = "김 에스더", userAge = "16", userSchoolName = "서울 중학교", - tag = "운동" ) ), MainItem.UserView( @@ -415,8 +413,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userId = "3", userName = "김 에스더", userAge = "16", - userSchoolName = "서울 중학교", - tag = "운동" + userSchoolName = "서울 중학교" ) ) ) @@ -455,8 +452,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userId = "1", userName = "김 에스더", userAge = "16", - userSchoolName = "서울 중학교", - tag = "운동" + userSchoolName = "서울 중학교" ) ), MainItem.UserView( @@ -466,7 +462,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userName = "김 에스더", userAge = "16", userSchoolName = "서울 중학교", - tag = "운동" ) ), MainItem.UserView( @@ -475,8 +470,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl userId = "3", userName = "김 에스더", userAge = "16", - userSchoolName = "서울 중학교", - tag = "운동" + userSchoolName = "서울 중학교" ) ) ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt index afd2fa32..ad7a396a 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt @@ -63,7 +63,6 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "서울 중학교", - tag = "운동" ), User( userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", @@ -71,7 +70,6 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "부천 중학교", - tag = "스터디" ), User( userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", @@ -79,7 +77,6 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "인천 중학교", - tag = "프로젝트" ) ) ) From 6eae296736865bbb4a4aac6495ff25cc389de338 Mon Sep 17 00:00:00 2001 From: YOON Date: Wed, 18 Dec 2024 14:59:48 +0900 Subject: [PATCH 02/15] =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20AP?= =?UTF-8?q?I=20=EC=97=B0=EA=B2=B0=20=EC=9E=91=EC=97=85=20-=20Guest?= =?UTF-8?q?=EC=99=80=20User=20=EA=B5=AC=EB=B6=84=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20UserType=20=EC=83=81=EC=88=98=20-=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20API=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20-=20User=20Response,=20UseCase=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20-=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=EC=9D=84=20=EC=9C=84=ED=95=B4=20tot?= =?UTF-8?q?alPage=20=EA=B0=92=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=AA=A9=EB=A1=9D=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eighteenandroid/common/Constants.kt | 4 +- .../common/ResourceProvider.kt | 18 ++ .../remote/response/UserResponse.kt | 20 +-- .../datasource/remote/service/UserService.kt | 4 +- .../eighteenandroid/data/mapper/UserMapper.kt | 49 ++++- .../data/repository/UserRepositoryImpl.kt | 41 ++--- .../domain/di/UserUseCaseModule.kt | 10 +- .../eighteenandroid/domain/model/User.kt | 3 +- .../domain/model/UserUseCaseModel.kt | 6 + .../domain/repository/UserRepository.kt | 5 +- .../domain/usecase/GetUserUseCase.kt | 13 ++ .../domain/usecase/UserUseCase.kt | 10 -- .../presentation/home/MainFragment.kt | 170 +++++------------- .../presentation/home/MainViewModel.kt | 154 ++++++++++++---- .../presentation/teen/TeenListFragment.kt | 3 + 15 files changed, 277 insertions(+), 233 deletions(-) create mode 100644 app/src/main/java/com/eighteen/eighteenandroid/common/ResourceProvider.kt create mode 100644 app/src/main/java/com/eighteen/eighteenandroid/domain/model/UserUseCaseModel.kt create mode 100644 app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt delete mode 100644 app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserUseCase.kt diff --git a/app/src/main/java/com/eighteen/eighteenandroid/common/Constants.kt b/app/src/main/java/com/eighteen/eighteenandroid/common/Constants.kt index 30d3f73f..57cfc478 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/common/Constants.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/common/Constants.kt @@ -1,3 +1,5 @@ package com.eighteen.eighteenandroid.common -const val MEDIA_COUNT = 2 \ No newline at end of file +const val MEDIA_COUNT = 2 +const val USER_TYPE_SIGNIN = "user" +const val USER_TYPE_GUEST = "guest" \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/common/ResourceProvider.kt b/app/src/main/java/com/eighteen/eighteenandroid/common/ResourceProvider.kt new file mode 100644 index 00000000..f298f2c2 --- /dev/null +++ b/app/src/main/java/com/eighteen/eighteenandroid/common/ResourceProvider.kt @@ -0,0 +1,18 @@ +package com.eighteen.eighteenandroid.common + +import android.content.Context +import androidx.annotation.StringRes +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class ResourceProvider @Inject constructor( + @ApplicationContext private val context: Context +) { + fun getString(@StringRes resId: Int): String { + return context.getString(resId) + } + + fun getString(@StringRes resId: Int, vararg args: Any): String { + return context.getString(resId, *args) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt index 0ce245e2..080bdf8c 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt @@ -4,16 +4,16 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class UserResponse( - @Json(name = "profileImage") - val userImage: String, - @Json(name = "uniqueId") - val userId: String, - @Json(name = "nickName") - val name: String, - @Json(name = "birthDate") - val birthDate: String, + val users: List, + val totalPage: Int +) + +data class UserDto( + val profileImage: String, + val uniqueId: String, + val nickName: String, + val birthDay: String, val location: String, val schoolName: String, - @Json(name = "likeStatus") - val isLike: Boolean + val likeStatus: Boolean ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt index 002f3340..bc1b6a69 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt @@ -29,8 +29,8 @@ interface UserService { suspend fun postLogin(@Query("phoneNumber") phoneNumber: String): Response> @GET("/v1/api/{userType}/find-all") - suspend fun getAllUser(@Path("userType") userType: String, @Query("page") page: Int, @Query("size") size: Int): Response>> + suspend fun getAllUser(@Path("userType") userType: String, @Query("page") page: Int, @Query("size") size: Int = 10): Response> @GET("/v1/api/{userType}/find-all-by-category/{category}") - suspend fun getCategoryUser(@Path("userType") userType: String, @Path("category") category: String, @Query("page") page: Int, @Query("size") size: Int) + suspend fun getCategoryUser(@Path("userType") userType: String, @Path("category") category: String, @Query("page") page: Int, @Query("size") size: Int = 10): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt index 2065194c..401f0366 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt @@ -1,17 +1,48 @@ package com.eighteen.eighteenandroid.data.mapper +import com.eighteen.eighteenandroid.data.datasource.remote.response.UserDto import com.eighteen.eighteenandroid.data.datasource.remote.response.UserResponse import com.eighteen.eighteenandroid.domain.model.User +import com.eighteen.eighteenandroid.domain.model.UserUseCaseModel +import java.time.LocalDate +import java.time.Period +import java.time.format.DateTimeFormatter object UserMapper { - fun asUserCaseModel(userResponse: UserResponse) = - userResponse.run { - User( - userImage = userImage, - userId = userId, - userName = name, - userAge = birthDate, /* TODO. 생년월일로 나이 구하기 */ - userSchoolName = schoolName - ) + private fun UserDto.toUser(): User { + fun calculateAge(birthDay: String): Int { + // 날짜 포맷터 생성 + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + + try { + // 문자열을 LocalDate 객체로 변환 + val birthDate = LocalDate.parse(birthDay, formatter) + + // 현재 날짜 가져오기 + val currentDate = LocalDate.now() + + // Period를 사용하여 두 날짜 사이의 기간 계산 + val period = Period.between(birthDate, currentDate) + + // 만 나이 계산 + return period.years + } catch (e: Exception) { + throw IllegalArgumentException("올바른 날짜 형식이 아닙니다. 'yyyy-MM-dd' 형식으로 입력해주세요.") + } } + + return User( + userImage = profileImage, + userId = uniqueId, + userName = nickName, + userAge = calculateAge(birthDay).toString(), + userSchoolName = schoolName, + likeStatus = likeStatus + ) + } + + fun UserResponse.toUserUseCaseModel() = UserUseCaseModel( + users = users.map { it.toUser() }, + totalPageCount = totalPage + ) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt index 4ec22c00..1e28cbab 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt @@ -12,6 +12,7 @@ import com.eighteen.eighteenandroid.data.datasource.remote.request.SchoolRequest import com.eighteen.eighteenandroid.data.datasource.remote.request.SignUpRequest import com.eighteen.eighteenandroid.data.datasource.remote.service.UserService import com.eighteen.eighteenandroid.data.mapper.ApiException +import com.eighteen.eighteenandroid.data.mapper.UserMapper.toUserUseCaseModel import com.eighteen.eighteenandroid.data.mapper.mapper import com.eighteen.eighteenandroid.domain.model.AuthToken import com.eighteen.eighteenandroid.domain.model.Mbti @@ -23,6 +24,7 @@ import com.eighteen.eighteenandroid.domain.model.School import com.eighteen.eighteenandroid.domain.model.SignUpInfo import com.eighteen.eighteenandroid.domain.model.SnsLink import com.eighteen.eighteenandroid.domain.model.User +import com.eighteen.eighteenandroid.domain.model.UserUseCaseModel import com.eighteen.eighteenandroid.domain.repository.UserRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,37 +34,18 @@ class UserRepositoryImpl @Inject constructor( private val userService: UserService, private val preferenceDatastore: DataStore ) : UserRepository { - override suspend fun fetchUserData(): Result> = + override suspend fun fetchAllUser(userType: String, page: Int): Result = runCatching { -// userService.getUserInfo().mapper { -// it.map { userResponse -> -// UserMapper.asUserCaseModel(userResponse) -// } -// } + userService.getAllUser(userType, page).mapper { + it.data?.toUserUseCaseModel() ?: UserUseCaseModel(emptyList(), 0) + } + } - listOf( - User( - userImage = "https://image.blip.kr/v1/file/021ec61ff1c9936943383b84236a0e69", - userId = "1", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교" - ), - User( - userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", - userId = "2", - userName = "김 에스더", - userAge = "16", - userSchoolName = "부천 중학교", - ), - User( - userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", - userId = "3", - userName = "김 에스더", - userAge = "16", - userSchoolName = "인천 중학교", - ) - ) + override suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result = + runCatching { + userService.getCategoryUser(userType, category, page).mapper { + it.data?.toUserUseCaseModel() ?: UserUseCaseModel(emptyList(), 0) + } } override suspend fun fetchUserDetailInfo(id: String): Result = runCatching { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt index 9d0878a2..94f298fa 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt @@ -3,11 +3,11 @@ package com.eighteen.eighteenandroid.domain.di import com.eighteen.eighteenandroid.domain.repository.MyPageRepository import com.eighteen.eighteenandroid.domain.repository.UserRepository import com.eighteen.eighteenandroid.domain.usecase.CheckIdDuplicationUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetUserUseCase import com.eighteen.eighteenandroid.domain.usecase.GetMyProfileUseCase import com.eighteen.eighteenandroid.domain.usecase.GetUserDetailInfoUseCase import com.eighteen.eighteenandroid.domain.usecase.LoginUseCase import com.eighteen.eighteenandroid.domain.usecase.SignUpUseCase -import com.eighteen.eighteenandroid.domain.usecase.UserUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,10 +17,6 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object UserUseCaseModule { - @Provides - @Singleton - fun provideUserUseCase(repository: UserRepository): UserUseCase = - UserUseCase(repository = repository) @Provides @Singleton @@ -44,4 +40,8 @@ object UserUseCaseModule { @Provides @Singleton fun provideLoginUseCase(repository: UserRepository) = LoginUseCase(repository = repository) + + @Provides + @Singleton + fun provideGetUserUseCase(repository: UserRepository) = GetUserUseCase(repository = repository) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt index 15df968b..cd7e802d 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt @@ -5,5 +5,6 @@ data class User( val userId: String, val userName: String, val userAge: String, - val userSchoolName: String + val userSchoolName: String, + val likeStatus: Boolean ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/UserUseCaseModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/UserUseCaseModel.kt new file mode 100644 index 00000000..488d20c5 --- /dev/null +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/UserUseCaseModel.kt @@ -0,0 +1,6 @@ +package com.eighteen.eighteenandroid.domain.model + +data class UserUseCaseModel( + val users: List, + val totalPageCount: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt index 1626c01f..4b2f5b92 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt @@ -3,15 +3,16 @@ package com.eighteen.eighteenandroid.domain.repository import com.eighteen.eighteenandroid.domain.model.AuthToken import com.eighteen.eighteenandroid.domain.model.Profile import com.eighteen.eighteenandroid.domain.model.SignUpInfo -import com.eighteen.eighteenandroid.domain.model.User +import com.eighteen.eighteenandroid.domain.model.UserUseCaseModel import kotlinx.coroutines.flow.Flow interface UserRepository { - suspend fun fetchUserData(): Result> suspend fun fetchUserDetailInfo(id: String): Result suspend fun postSignUp(signUpInfo: SignUpInfo): Result suspend fun checkIdDuplication(uniqueId: String): Result fun getTokenFlow(): Flow suspend fun saveToken(authToken: AuthToken) suspend fun login(phoneNumber: String): Result + suspend fun fetchAllUser(userType: String, page: Int): Result + suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt new file mode 100644 index 00000000..9ab7baa8 --- /dev/null +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt @@ -0,0 +1,13 @@ +package com.eighteen.eighteenandroid.domain.usecase + +import com.eighteen.eighteenandroid.common.enums.Tag +import com.eighteen.eighteenandroid.domain.repository.UserRepository +import javax.inject.Inject + +class GetUserUseCase @Inject constructor(private val repository: UserRepository) { + suspend operator fun invoke(category: String, userType: String, page: Int) = if(category == Tag.ALL.name) { + repository.fetchAllUser(userType, page) + } else { + repository.fetchCategoryUser(userType, category, page) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserUseCase.kt deleted file mode 100644 index ce3f11a3..00000000 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.eighteen.eighteenandroid.domain.usecase - -import com.eighteen.eighteenandroid.domain.model.User -import com.eighteen.eighteenandroid.domain.repository.UserRepository -import javax.inject.Inject - -class UserUseCase @Inject constructor(private val repository: UserRepository) { - suspend fun invoke(): Result> = - repository.fetchUserData() -} \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index b18b910f..77d94883 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -15,10 +15,11 @@ import com.eighteen.eighteenandroid.R import com.eighteen.eighteenandroid.common.enums.Tag import com.eighteen.eighteenandroid.databinding.FragmentMainBinding import com.eighteen.eighteenandroid.domain.model.AboutTeen -import com.eighteen.eighteenandroid.domain.model.MainItem import com.eighteen.eighteenandroid.domain.model.Tournament import com.eighteen.eighteenandroid.domain.model.User import com.eighteen.eighteenandroid.presentation.BaseFragment +import com.eighteen.eighteenandroid.presentation.common.ModelState +import com.eighteen.eighteenandroid.presentation.common.collectInLifecycle import com.eighteen.eighteenandroid.presentation.common.createChip import com.eighteen.eighteenandroid.presentation.common.findViewHolderOrNull import com.eighteen.eighteenandroid.presentation.common.setTagStyle @@ -47,9 +48,8 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var selectedChip: Chip? = null private lateinit var mainAdapter: MainAdapter - private var userList = listOf() - private var aboutTeenList = listOf() - private var tournamentList = listOf() +// private var aboutTeenList = listOf() +// private var tournamentList = listOf() private lateinit var mainAdapterListener: MainAdapterListener @@ -60,6 +60,8 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false + private var pageNumList = mutableListOf() + override fun initView() { initChipGroup() initMain() @@ -345,136 +347,37 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private fun initMain() { initMainAdapter() - initData() - initUserListObserver() + initMainItemObserver() } - private fun initUserListObserver() { -// viewModel.mainItems.observe(viewLifecycleOwner) { -// mainAdapter.updateView(it) -// } - - viewModel.userData.observe(viewLifecycleOwner) { - userList = it - updateMain() + private fun initMainItemObserver() { + viewModel.totalPage.observe(viewLifecycleOwner) { + // 총 페이지가 1보다 많을 때 + if( it > 1 ) { + for( i in 1 until it ) { + pageNumList.add(i) + } + } } - } - private fun updateMain() { - mainAdapter.updateView( - listOf( - MainItem.HeaderView(resources.getString(R.string.main_today_teen)), - MainItem.UserListView( - userList - ), // User List - MainItem.DividerView, - MainItem.HeaderView(getString(R.string.main_about_teen)), - MainItem.AboutTeenListView( -// aboutTeenList - listOf( - AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), - AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), - AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), - AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") - ) - ), // About Teen List - MainItem.DividerView, - MainItem.HeaderWithMoreView(getString(R.string.main_tournament_in_progress)), - MainItem.TournamentListView( -// tournamentList - listOf( - Tournament.Exercise, - Tournament.Study - ) - ), // Tournament List - MainItem.DividerView, - MainItem.HeaderView(getString(R.string.main_another_teen)), - MainItem.UserView( - User( - userImage = "https://image.blip.kr/v1/file/021ec61ff1c9936943383b84236a0e69", - userId = "1", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교" - ) - ), - MainItem.UserView( - User( - userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", - userId = "2", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교", - ) - ), - MainItem.UserView( - User( - userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", - userId = "3", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교" - ) - ) - ) - ) - } + collectInLifecycle(viewModel.mainItemStateFlow) { + when(it) { + is ModelState.Loading -> { - private fun initData() { - mainAdapter.updateView( - listOf( - MainItem.UserListView( - emptyList() - ), // User List - MainItem.DividerView, - MainItem.HeaderView(getString(R.string.main_about_teen)), - MainItem.AboutTeenListView( - listOf( - AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), - AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), - AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), - AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") - ) - ), // About Teen List - MainItem.DividerView, - MainItem.HeaderWithMoreView(getString(R.string.main_tournament_in_progress)), - MainItem.TournamentListView( - listOf( - Tournament.Exercise, - Tournament.Study - ) - ), // Tournament List - MainItem.DividerView, - MainItem.HeaderView(getString(R.string.main_another_teen)), - MainItem.UserView( - User( - userImage = "https://image.blip.kr/v1/file/021ec61ff1c9936943383b84236a0e69", - userId = "1", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교" - ) - ), - MainItem.UserView( - User( - userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", - userId = "2", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교", - ) - ), - MainItem.UserView( - User( - userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", - userId = "3", - userName = "김 에스더", - userAge = "16", - userSchoolName = "서울 중학교" - ) - ) - ) - ) + } + is ModelState.Success -> { + it.data?.let { mainItems -> + mainAdapter.updateView(mainItems) + } + } + is ModelState.Error -> { + + } + else ->{ + //do nothing + } + } + } } private fun initChipGroup() { @@ -488,11 +391,20 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl selectedChip?.setTagStyle(isBlackBackground = false) chip.setTagStyle(isBlackBackground = true) selectedChip = chip + + getUserData(tag) // 현재 카테고리에 맞는 데이터 가져오기 } bind { chipGroup.addView(chip) } } + + // 전체 유저 가져오기 + // 페이지 0부터 + } + + private fun getUserData(tag: Tag) { + viewModel.initMain(tag) } override fun onResume() { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 8924b1ab..899f711b 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -1,76 +1,160 @@ package com.eighteen.eighteenandroid.presentation.home -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.eighteen.eighteenandroid.R +import com.eighteen.eighteenandroid.common.ResourceProvider +import com.eighteen.eighteenandroid.common.USER_TYPE_SIGNIN +import com.eighteen.eighteenandroid.common.USER_TYPE_GUEST +import com.eighteen.eighteenandroid.common.enums.Tag +import com.eighteen.eighteenandroid.domain.model.AboutTeen import com.eighteen.eighteenandroid.domain.model.MainItem -import com.eighteen.eighteenandroid.domain.model.User -import com.eighteen.eighteenandroid.domain.usecase.UserUseCase +import com.eighteen.eighteenandroid.domain.model.Tournament +import com.eighteen.eighteenandroid.domain.usecase.GetUserUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetAuthTokenFlowUseCase +import com.eighteen.eighteenandroid.presentation.common.ModelState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( - private val userUseCase: UserUseCase + private val resourceProvider: ResourceProvider, + private val getUserUseCase: GetUserUseCase, + private val getAuthTokenFlowUseCase: GetAuthTokenFlowUseCase ) : ViewModel() { var popularUserPosition = 0 var pageScrollPosition = 0 - private val _userData = MutableLiveData>() - val userData: LiveData> = _userData + private val _mainItemStateFlow = MutableStateFlow>>(ModelState.Empty()) + val mainItemStateFlow: StateFlow>> + get() = _mainItemStateFlow.asStateFlow() - private val _mainItems = MutableLiveData>() - val mainItems: LiveData> - get() = _mainItems + /** 현재 페이지 정보 */ + private var currentPage = 0 + + /** 전체 페이지 수 */ + private val _totalPage = MutableLiveData() + val totalPage: LiveData + get() = _totalPage + + /** Category tag */ + private val _category = MutableLiveData() + val category: LiveData + get() = _category init { - updateMain() + initMain(Tag.ALL) } - private fun updateMain() { - val items = mutableListOf() + /** 화면 초기화 */ + private fun resetPage() { + currentPage = 0 + } + + fun setPage(page: Int) { + currentPage = page + } - // 둘 중 하나 에러나면 다시 전체 가져오기 + fun initMain(category: Tag) { + val items = mutableListOf() viewModelScope.launch { - fetchUserData() -// val userDataState = fetchUserData().await() -// val aboutTeenDataState = fetchUserData().await() - // 토너먼트 - // 1페이지 + _mainItemStateFlow.value = ModelState.Loading() + + resetPage() + + // 로그인 상태 확인 + val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( + viewModelScope, + SharingStarted.Eagerly, null + ) + + // TODO. val popularTeenInitialState (인기 Teen) + + items.addAll( + listOf( + MainItem.HeaderView(resourceProvider.getString(R.string.main_today_teen)), + MainItem.UserListView( + emptyList() + ), // TODO. 인기 Teen + MainItem.DividerView, + MainItem.HeaderView(resourceProvider.getString(R.string.main_about_teen)), + MainItem.AboutTeenListView( + listOf( + AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), + AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), + AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), + AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") + ) + ), // About Teen List + MainItem.DividerView, + MainItem.HeaderWithMoreView(resourceProvider.getString(R.string.main_tournament_in_progress)), + MainItem.TournamentListView( + listOf( + Tournament.Exercise, + Tournament.Study + ) + ), // TODO. Tournament List + MainItem.DividerView, + MainItem.HeaderView(resourceProvider.getString(R.string.main_another_teen)) + ) + ) + + val anotherTeenInitialState = if(authTokenStateFlow.value == null) { + // 게스트 + fetchAnotherUser(category, USER_TYPE_GUEST, 0).await() + } else { + // 유저 + fetchAnotherUser(category, USER_TYPE_SIGNIN, 0).await() + } + + anotherTeenInitialState.onSuccess { + _totalPage.postValue(it.totalPageCount) // 총 페이지 + + it.users.forEach { user -> + items.add( + MainItem.UserView(user) + ) + } + + _mainItemStateFlow.value = ModelState.Success(items) + }.onFailure { e -> + _mainItemStateFlow.value = ModelState.Error(throwable = e) + } + // if( userDataState is ModelState.Success && aboutTeenDataState is ModelState.Success ) { -// // // updateMain // } else { // // 에러화면 // } - - _mainItems.value = items } } - /*fun 무한_스크롤() { - val items = mainItems.value // 기존 데이터 - items.add(새로 받아온 놈들) - _mainItems.value = items - }*/ - - private suspend fun fetchUserData() = viewModelScope.async { - userUseCase.invoke().onSuccess { -// ModelState.Success(it) - _userData.value = it - }.onFailure { e -> - Log.e(TAG, e.toString()) -// ModelState.Error(e) - } + private fun fetchAnotherUser(category: Tag, userType: String, page: Int) = viewModelScope.async { + getUserUseCase.invoke(category.name, userType, page) } +// private suspend fun fetchUserData() = viewModelScope.async { +// userUseCase.invoke().onSuccess { +//// ModelState.Success(it) +// _userData.value = it +// }.onFailure { e -> +// Log.e(TAG, e.toString()) +//// ModelState.Error(e) +// } +// } + companion object { const val TAG = "MainViewModel" } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt index ad7a396a..d865dbd7 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt @@ -63,6 +63,7 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "서울 중학교", + likeStatus = true ), User( userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", @@ -70,6 +71,7 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "부천 중학교", + likeStatus = false ), User( userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", @@ -77,6 +79,7 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi userName = "김 에스더", userAge = "16", userSchoolName = "인천 중학교", + likeStatus = false ) ) ) From 6b9b4ab5a19818092c38d039ea4d0cf3d2bb1c46 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 19 Dec 2024 16:18:10 +0900 Subject: [PATCH 03/15] =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20ap?= =?UTF-8?q?i=20-=20=EB=8B=A4=EC=9D=8C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20requ?= =?UTF-8?q?est=20api=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=A0=81=EC=9A=A9=20-=20?= =?UTF-8?q?=ED=98=84=EC=9E=AC=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC,=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=9C=84=EC=B9=98=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20-=20=EC=9C=A0=EC=A0=80=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=20=ED=8C=A8=EB=94=A9=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/MainFragment.kt | 107 +++++++++++++----- .../presentation/home/MainViewModel.kt | 104 +++++++++++------ .../presentation/home/adapter/MainAdapter.kt | 18 ++- 3 files changed, 161 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 77d94883..33463235 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.launch class MainFragment : BaseFragment(FragmentMainBinding::inflate) { private val viewModel by viewModels() - private var selectedChip: Chip? = null private lateinit var mainAdapter: MainAdapter // private var aboutTeenList = listOf() @@ -60,7 +59,11 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false - private var pageNumList = mutableListOf() + // 현재 카테고리 + private var selectedChip: Chip? = null // 칩 버튼 View + private var category: Tag = Tag.ALL // 카테고리 정보 + + private lateinit var _pageNumList: List override fun initView() { initChipGroup() @@ -122,6 +125,29 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl btnScrollTop.startAnimation(fadeIn) btnScrollTop.visibility = View.VISIBLE } + + val layoutManager = (layoutManager as? LinearLayoutManager) + + val lastVisibleItemPosition = layoutManager?.findLastCompletelyVisibleItemPosition() + val itemTotalCount = recyclerView.adapter!!.itemCount-1 + + if (!canScrollVertically(-1)) { + isTop = true + } else { + isTop = false + + // 아래로 더 이동할 수 없을 때 + if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { + if(_pageNumList.isNotEmpty()) { + // 남은 페이지 유저 목록이 있다면 + val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 + viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 + + // page 정보 가져오기 + viewModel.requestNextPage(category, page) + } + } + } } override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { @@ -351,13 +377,9 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl } private fun initMainItemObserver() { - viewModel.totalPage.observe(viewLifecycleOwner) { - // 총 페이지가 1보다 많을 때 - if( it > 1 ) { - for( i in 1 until it ) { - pageNumList.add(i) - } - } + // 남은 페이지 목록 + viewModel.pageNumList.observe(viewLifecycleOwner) { + _pageNumList = it } collectInLifecycle(viewModel.mainItemStateFlow) { @@ -378,6 +400,31 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl } } } + + collectInLifecycle(viewModel.appendStateFlow) { + when(it) { + is ModelState.Loading -> { + // do nothing + // mainAdapter.addLoadingView() + } + is ModelState.Success -> { + it.data?.let { mainItems -> + // mainAdapter.removeLoadingView() + mainAdapter.appendItems(mainItems) { + // after Notify -> 기존 스크롤 유지 + moveToSavedPosition() + } + } + } + is ModelState.Error -> { + + } + else -> { + //do nothing + } + } + } + } private fun initChipGroup() { @@ -391,6 +438,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl selectedChip?.setTagStyle(isBlackBackground = false) chip.setTagStyle(isBlackBackground = true) selectedChip = chip + category = tag // 현재 카테고리 값 저장 getUserData(tag) // 현재 카테고리에 맞는 데이터 가져오기 } @@ -407,27 +455,30 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl viewModel.initMain(tag) } - override fun onResume() { - super.onResume() + private fun moveToSavedPosition() { lifecycleScope.launch { delay(300) -// bind { -// // 마지막 스크롤 상태로 돌아오기 -// if (viewModel.pageScrollPosition != -1) { -// -// rvMain.scrollToPosition(viewModel.pageScrollPosition) -// -// // LayoutManager에서 해당 위치의 아이템을 중앙에 위치시키도록 오프셋 조정 -// rvMain.post { -// val layoutManager = rvMain.layoutManager as LinearLayoutManager -// val viewAtPosition = layoutManager.findViewByPosition(viewModel.pageScrollPosition) -// if (viewAtPosition != null) { -// val offset = (rvMain.height - viewAtPosition.height) / 2 -// layoutManager.scrollToPositionWithOffset(viewModel.pageScrollPosition, offset) -// } -// } -// } -// } + bind { + // 마지막 스크롤 상태로 돌아오기 + if (viewModel.pageScrollPosition != -1) { + + rvMain.scrollToPosition(viewModel.pageScrollPosition) + + // LayoutManager에서 해당 위치의 아이템을 중앙에 위치시키도록 오프셋 조정 + rvMain.post { + val layoutManager = rvMain.layoutManager as LinearLayoutManager + val viewAtPosition = + layoutManager.findViewByPosition(viewModel.pageScrollPosition) + if (viewAtPosition != null) { + val offset = (rvMain.height - viewAtPosition.height) / 2 + layoutManager.scrollToPositionWithOffset( + viewModel.pageScrollPosition, + offset + ) + } + } + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 899f711b..3d900809 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -35,34 +35,31 @@ class MainViewModel @Inject constructor( var popularUserPosition = 0 var pageScrollPosition = 0 + /** 메인화면 전체 데이터 */ private val _mainItemStateFlow = MutableStateFlow>>(ModelState.Empty()) val mainItemStateFlow: StateFlow>> get() = _mainItemStateFlow.asStateFlow() - /** 현재 페이지 정보 */ - private var currentPage = 0 + /** 무한 스크롤을 위한 새로 불러온 데이터 */ + private val _appendStateFlow = MutableStateFlow>>(ModelState.Empty()) + val appendStateFlow: StateFlow>> + get() = _appendStateFlow.asStateFlow() - /** 전체 페이지 수 */ - private val _totalPage = MutableLiveData() - val totalPage: LiveData - get() = _totalPage - - /** Category tag */ - private val _category = MutableLiveData() - val category: LiveData - get() = _category + /** 남은 페이지 목록 */ + private val _pageNumList = MutableLiveData>() + val pageNumList: LiveData> + get() = _pageNumList init { initMain(Tag.ALL) } - /** 화면 초기화 */ - private fun resetPage() { - currentPage = 0 - } - - fun setPage(page: Int) { - currentPage = page + fun removePage(page: Int) { + val pages = _pageNumList.value + pages?.let { + it.remove(page) + _pageNumList.postValue(it) + } } fun initMain(category: Tag) { @@ -71,14 +68,6 @@ class MainViewModel @Inject constructor( viewModelScope.launch { _mainItemStateFlow.value = ModelState.Loading() - resetPage() - - // 로그인 상태 확인 - val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( - viewModelScope, - SharingStarted.Eagerly, null - ) - // TODO. val popularTeenInitialState (인기 Teen) items.addAll( @@ -110,6 +99,12 @@ class MainViewModel @Inject constructor( ) ) + // 로그인 상태 확인 + val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( + viewModelScope, + SharingStarted.Eagerly, null + ) + val anotherTeenInitialState = if(authTokenStateFlow.value == null) { // 게스트 fetchAnotherUser(category, USER_TYPE_GUEST, 0).await() @@ -119,7 +114,16 @@ class MainViewModel @Inject constructor( } anotherTeenInitialState.onSuccess { - _totalPage.postValue(it.totalPageCount) // 총 페이지 + if(it.totalPageCount > 1) { + val pages = mutableListOf() + + // 첫 번째 페이지(0)를 제외한 페이지 목록 + for (page in 1 until it.totalPageCount) { + pages.add(page) + } + + _pageNumList.postValue(pages) + } it.users.forEach { user -> items.add( @@ -141,20 +145,46 @@ class MainViewModel @Inject constructor( } } + /** 또 다른 Teen 무한 스크롤, 다음 페이지 유저 정보 가져오기 */ + fun requestNextPage(category: Tag, page: Int) { + viewModelScope.launch { + val items = mutableListOf() + + // 로딩 시작 + _appendStateFlow.value = ModelState.Loading() + + // 로그인 상태 확인 + val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( + viewModelScope, + SharingStarted.Eagerly, null + ) + + val anotherTeenInitialState = if(authTokenStateFlow.value == null) { + // 게스트 + fetchAnotherUser(category, USER_TYPE_GUEST, page).await() + } else { + // 유저 + fetchAnotherUser(category, USER_TYPE_SIGNIN, page).await() + } + + anotherTeenInitialState.onSuccess { + it.users.forEach { user -> + items.add( + MainItem.UserView(user) + ) + } + + _appendStateFlow.value = ModelState.Success(items) + }.onFailure { e -> + _appendStateFlow.value = ModelState.Error(throwable = e) + } + } + } + private fun fetchAnotherUser(category: Tag, userType: String, page: Int) = viewModelScope.async { getUserUseCase.invoke(category.name, userType, page) } -// private suspend fun fetchUserData() = viewModelScope.async { -// userUseCase.invoke().onSuccess { -//// ModelState.Success(it) -// _userData.value = it -// }.onFailure { e -> -// Log.e(TAG, e.toString()) -//// ModelState.Error(e) -// } -// } - companion object { const val TAG = "MainViewModel" } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index a4ef5fd5..362af387 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -258,9 +258,6 @@ class MainAdapter( private val listener: MainAdapterListener ) : CommonViewHolder(binding) { fun bind(item: MainItem, itemCount: Int, position: Int) { - if(itemCount -1 == position) { - itemView.setPadding(0, 0, 0, context.dp2Px(60)) - } val userView = item as? MainItem.UserView with(binding) { userView?.let { @@ -332,4 +329,19 @@ class MainAdapter( fun updateView(list: List) { submitList(list) } + + // 기존 데이터를 보존하면서 새 데이터만 추가하는 메소드 + fun appendItems(newItems: List, afterNotify: () -> Unit) { + val currentList = currentList.toMutableList() + val startPosition = currentList.size + + listener.saveScrollPosition(startPosition - 1) // 기존 스크롤 위치 저장 + currentList.addAll(newItems) + + submitList(currentList) { + // submitList 콜백에서 새로운 아이템에 대해서만 notify + notifyItemRangeInserted(startPosition, newItems.size) + afterNotify() + } + } } \ No newline at end of file From af9d83015f3abd2bc47eca656a0337ef6da85f91 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 19 Dec 2024 17:38:02 +0900 Subject: [PATCH 04/15] =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=20=EB=B7=B0=20=EC=B6=94=EA=B0=80=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 ++ .../eighteenandroid/domain/model/MainItem.kt | 3 ++ .../presentation/home/MainFragment.kt | 18 ++++----- .../presentation/home/MainViewModel.kt | 2 + .../presentation/home/adapter/MainAdapter.kt | 38 ++++++++++++++++++- app/src/main/res/layout/item_main_loading.xml | 19 ++++++++++ app/src/main/res/raw/loading.json | 1 + 7 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/layout/item_main_loading.xml create mode 100644 app/src/main/res/raw/loading.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 27e6e9d6..1d4bacf2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -174,4 +174,8 @@ dependencies { //DataStore val datastoreVersion = "1.1.1" implementation("androidx.datastore:datastore-preferences:$datastoreVersion") + + // Lottie + val lottieVersion = "5.0.2" + implementation("com.airbnb.android:lottie:$lottieVersion") } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/MainItem.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/MainItem.kt index 21cc5f00..ad74eb50 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/MainItem.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/MainItem.kt @@ -33,4 +33,7 @@ sealed class MainItem{ data class UserView( val user: User ): MainItem() + + // LoadingView + object LoadingView: MainItem() } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 33463235..98d4222e 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -21,6 +21,7 @@ import com.eighteen.eighteenandroid.presentation.BaseFragment import com.eighteen.eighteenandroid.presentation.common.ModelState import com.eighteen.eighteenandroid.presentation.common.collectInLifecycle import com.eighteen.eighteenandroid.presentation.common.createChip +import com.eighteen.eighteenandroid.presentation.common.dp2Px import com.eighteen.eighteenandroid.presentation.common.findViewHolderOrNull import com.eighteen.eighteenandroid.presentation.common.setTagStyle import com.eighteen.eighteenandroid.presentation.common.showDialogFragment @@ -145,6 +146,8 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl // page 정보 가져오기 viewModel.requestNextPage(category, page) + } else { + binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) } } } @@ -153,12 +156,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) - if (!canScrollVertically(-1)) { - isTop = true - } else { - isTop = false - } - val layoutManager = (layoutManager as? LinearLayoutManager) // Log.i("MainScrollStateChanged", "findLastVisible = ${layoutManager?.findLastVisibleItemPosition().toString()}") @@ -405,12 +402,13 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl when(it) { is ModelState.Loading -> { // do nothing - // mainAdapter.addLoadingView() + mainAdapter.addLoadingView() { + moveToSavedPosition() + } } is ModelState.Success -> { - it.data?.let { mainItems -> - // mainAdapter.removeLoadingView() - mainAdapter.appendItems(mainItems) { + it.data?.let { newItems -> + mainAdapter.appendItems(newItems) { // after Notify -> 기존 스크롤 유지 moveToSavedPosition() } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 3d900809..50e2de2b 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -17,6 +17,7 @@ import com.eighteen.eighteenandroid.domain.usecase.GetAuthTokenFlowUseCase import com.eighteen.eighteenandroid.presentation.common.ModelState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -152,6 +153,7 @@ class MainViewModel @Inject constructor( // 로딩 시작 _appendStateFlow.value = ModelState.Loading() + delay(1000) // 로그인 상태 확인 val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index 362af387..f0e743b9 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -15,6 +15,7 @@ import com.eighteen.eighteenandroid.databinding.ItemDividerBinding import com.eighteen.eighteenandroid.databinding.ItemMainAboutTeenListviewBinding import com.eighteen.eighteenandroid.databinding.ItemMainHeaderBinding import com.eighteen.eighteenandroid.databinding.ItemMainHeaderMoreBinding +import com.eighteen.eighteenandroid.databinding.ItemMainLoadingBinding import com.eighteen.eighteenandroid.databinding.ItemMainPopularTeenListviewBinding import com.eighteen.eighteenandroid.databinding.ItemMainTournamentListviewBinding import com.eighteen.eighteenandroid.databinding.ItemTeenBinding @@ -71,6 +72,7 @@ class MainAdapter( const val USER_VIEW = 5 const val ABOUT_TEEN_LIST = 6 const val TOURNAMENT_LIST = 7 + const val LOADING_VIEW = 8 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder { @@ -114,6 +116,11 @@ class MainAdapter( CommonViewHolder.TournamentListViewHolder(itemBinding, listener) } + LOADING_VIEW -> { + val itemBinding = ItemMainLoadingBinding.inflate(layoutInflater, parent, false) + CommonViewHolder.LoadingViewHolder(itemBinding) + } + else -> throw IllegalArgumentException("Invalid ViewType") } } @@ -148,6 +155,10 @@ class MainAdapter( (getItem(position) as? MainItem.DividerView)?.let { holder.bind(it) } } + is CommonViewHolder.LoadingViewHolder -> { + (getItem(position) as? MainItem.LoadingView)?.let { holder.bind((it)) } + } + else -> throw IllegalArgumentException("Invalid ViewHolder") } } @@ -161,6 +172,7 @@ class MainAdapter( is MainItem.AboutTeenListView -> ABOUT_TEEN_LIST is MainItem.TournamentListView -> TOURNAMENT_LIST is MainItem.UserView -> USER_VIEW + is MainItem.LoadingView -> LOADING_VIEW } } @@ -324,6 +336,14 @@ class MainAdapter( // ... } } + + class LoadingViewHolder( + private val binding: ItemMainLoadingBinding + ): CommonViewHolder(binding) { + fun bind(item: MainItem) { + // ... + } + } } fun updateView(list: List) { @@ -333,8 +353,10 @@ class MainAdapter( // 기존 데이터를 보존하면서 새 데이터만 추가하는 메소드 fun appendItems(newItems: List, afterNotify: () -> Unit) { val currentList = currentList.toMutableList() - val startPosition = currentList.size + currentList.remove(MainItem.LoadingView) + notifyItemRangeRemoved(currentList.size, 1) + val startPosition = currentList.size listener.saveScrollPosition(startPosition - 1) // 기존 스크롤 위치 저장 currentList.addAll(newItems) @@ -344,4 +366,18 @@ class MainAdapter( afterNotify() } } + + fun addLoadingView( afterNotify: () -> Unit ) { + val currentList = currentList.toMutableList() + val startPosition = currentList.size + + currentList.add(MainItem.LoadingView) + listener.saveScrollPosition(startPosition - 1) // 기존 스크롤 위치 저장 + + submitList(currentList) { + // submitList 콜백에서 새로운 아이템에 대해서만 notify + notifyItemRangeInserted(startPosition, 1) + afterNotify() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_main_loading.xml b/app/src/main/res/layout/item_main_loading.xml new file mode 100644 index 00000000..63173d98 --- /dev/null +++ b/app/src/main/res/layout/item_main_loading.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/loading.json b/app/src/main/res/raw/loading.json new file mode 100644 index 00000000..acd137a2 --- /dev/null +++ b/app/src/main/res/raw/loading.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":60,"ip":0,"op":61,"w":600,"h":600,"nm":"ios-spinner","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-180,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":2,"ty":4,"nm":"2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[8]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":60,"s":[8]}],"ix":11},"r":{"a":0,"k":-150,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":3,"ty":4,"nm":"3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[16]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"t":60,"s":[16]}],"ix":11},"r":{"a":0,"k":-120,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":4,"ty":4,"nm":"4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[100]},{"t":60,"s":[24]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":5,"ty":4,"nm":"5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[32]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[100]},{"t":60,"s":[32]}],"ix":11},"r":{"a":0,"k":-60,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":6,"ty":4,"nm":"6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[40]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":24,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[100]},{"t":60,"s":[40]}],"ix":11},"r":{"a":0,"k":-30,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":7,"ty":4,"nm":"7","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[48]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[100]},{"t":60,"s":[48]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":8,"ty":4,"nm":"8","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[56]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":34,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":35,"s":[100]},{"t":60,"s":[56]}],"ix":11},"r":{"a":0,"k":-330,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":9,"ty":4,"nm":"9","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[64]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":39,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[100]},{"t":60,"s":[64]}],"ix":11},"r":{"a":0,"k":-300,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":10,"ty":4,"nm":"10","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[72]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"t":60,"s":[72]}],"ix":11},"r":{"a":0,"k":-270,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":11,"ty":4,"nm":"11","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[80]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":49,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[100]},{"t":60,"s":[80]}],"ix":11},"r":{"a":0,"k":-240,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true},{"ddd":0,"ind":12,"ty":4,"nm":"12","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[88]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":54,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":55,"s":[100]},{"t":60,"s":[88]}],"ix":11},"r":{"a":0,"k":-210,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13,22],[13,124]],"o":[[13,22],[13,124]],"v":[[13,22],[13,124]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false,"_render":true},{"ty":"st","c":{"a":0,"k":[0.1804,0.0118,0.898,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false,"_render":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[11,-82],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform","_render":true}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"_render":true}],"ip":0,"op":61,"st":0,"bm":0,"completed":true}],"markers":[],"__complete":true} \ No newline at end of file From f2fad764074da34b902893b352e26b717f339203 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 26 Dec 2024 11:23:58 +0900 Subject: [PATCH 05/15] =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EB=B7=B0=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EC=A1=B0=EC=A0=95=20=EB=B0=8F=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=B6=94=EA=B0=80=EB=90=98=EB=8D=98=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/MainFragment.kt | 28 ++++++++++++------- app/src/main/res/layout/item_main_loading.xml | 4 +-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 98d4222e..47e07e1f 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -59,6 +59,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false + private var isLoading = false // 현재 카테고리 private var selectedChip: Chip? = null // 칩 버튼 View @@ -139,15 +140,20 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl // 아래로 더 이동할 수 없을 때 if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { - if(_pageNumList.isNotEmpty()) { - // 남은 페이지 유저 목록이 있다면 - val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 - viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 - - // page 정보 가져오기 - viewModel.requestNextPage(category, page) + if(_pageNumList.isEmpty()) { + binding.rvMain.post { + binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) + moveToSavedPosition() + } } else { - binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) + if(!isLoading) { + // 남은 페이지 유저 목록이 있다면 + val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 + viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 + + // page 정보 가져오기 + viewModel.requestNextPage(category, page) + } } } } @@ -401,12 +407,15 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl collectInLifecycle(viewModel.appendStateFlow) { when(it) { is ModelState.Loading -> { - // do nothing + isLoading = true + mainAdapter.addLoadingView() { moveToSavedPosition() } } is ModelState.Success -> { + isLoading = false + it.data?.let { newItems -> mainAdapter.appendItems(newItems) { // after Notify -> 기존 스크롤 유지 @@ -455,7 +464,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private fun moveToSavedPosition() { lifecycleScope.launch { - delay(300) bind { // 마지막 스크롤 상태로 돌아오기 if (viewModel.pageScrollPosition != -1) { diff --git a/app/src/main/res/layout/item_main_loading.xml b/app/src/main/res/layout/item_main_loading.xml index 63173d98..99e64542 100644 --- a/app/src/main/res/layout/item_main_loading.xml +++ b/app/src/main/res/layout/item_main_loading.xml @@ -14,6 +14,6 @@ app:lottie_autoPlay="true" app:lottie_loop="true" app:lottie_rawRes="@raw/loading" - android:layout_width="47dp" - android:layout_height="47dp" /> + android:layout_width="16dp" + android:layout_height="16dp" /> \ No newline at end of file From 603ace23bd66eac6c49c946497b9f088f99a1318 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 26 Dec 2024 11:33:12 +0900 Subject: [PATCH 06/15] =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=9D=BC=20=EB=95=8C,?= =?UTF-8?q?=20=EC=95=84=EB=9E=98=20Padding=EC=9C=BC=EB=A1=9C=20=EA=B0=84?= =?UTF-8?q?=EA=B2=A9=20=EC=84=A4=EC=A0=95=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/MainFragment.kt | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 47e07e1f..13b404f3 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -60,6 +60,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false private var isLoading = false + private var isLastPage = false // 현재 카테고리 private var selectedChip: Chip? = null // 칩 버튼 View @@ -133,30 +134,68 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl val lastVisibleItemPosition = layoutManager?.findLastCompletelyVisibleItemPosition() val itemTotalCount = recyclerView.adapter!!.itemCount-1 - if (!canScrollVertically(-1)) { - isTop = true + if(isLastPage) { + // isLastPage가 true일 때 + if(lastVisibleItemPosition == itemTotalCount) { + // 최하단에 도달했을 때만 60dp + rvMain.post { + rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) + moveToSavedPosition() + } + } else { + // 그 외에는 20dp + binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(20)) + } } else { - isTop = false + // isLastPage가 false일 때는 항상 20dp + binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(20)) - // 아래로 더 이동할 수 없을 때 if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { if(_pageNumList.isEmpty()) { + isLastPage = true binding.rvMain.post { binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) moveToSavedPosition() } } else { + isLastPage = false if(!isLoading) { - // 남은 페이지 유저 목록이 있다면 - val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 - viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 - - // page 정보 가져오기 + val page = _pageNumList.random() + viewModel.removePage(page) viewModel.requestNextPage(category, page) } } } } + + if (!canScrollVertically(-1)) { + isTop = true + } else { + isTop = false + + // 아래로 더 이동할 수 없을 때 +// if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { +// if(_pageNumList.isEmpty()) { +// isLastPage = true +// +// binding.rvMain.post { +// binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) +// moveToSavedPosition() +// } +// } else { +// isLastPage = false +// +// if(!isLoading) { +// // 남은 페이지 유저 목록이 있다면 +// val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 +// viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 +// +// // page 정보 가져오기 +// viewModel.requestNextPage(category, page) +// } +// } +// } + } } override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { From ec944f782af63949d0f4b9f38bad609789bfebba Mon Sep 17 00:00:00 2001 From: yys7517 Date: Fri, 27 Dec 2024 14:21:39 +0900 Subject: [PATCH 07/15] =?UTF-8?q?-=20=EC=9C=A0=EC=A0=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94,=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20API=20=EC=97=B0=EA=B2=B0=20-=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EC=A2=8B=EC=95=84=EC=9A=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?userId=20Int=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20-=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=ED=99=94=EB=A9=B4=20notify=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remote/response/UserResponse.kt | 3 +- .../datasource/remote/service/UserService.kt | 7 + .../eighteenandroid/data/mapper/UserMapper.kt | 5 +- .../data/repository/UserRepositoryImpl.kt | 14 ++ .../domain/di/UserUseCaseModule.kt | 5 + .../eighteenandroid/domain/model/User.kt | 5 +- .../domain/repository/UserRepository.kt | 2 + .../domain/usecase/UserLikeUseCase.kt | 13 ++ .../dialog/ReportDialogFragment.kt | 2 +- .../presentation/home/MainFragment.kt | 196 ++++++------------ .../presentation/home/MainViewModel.kt | 95 ++++++--- .../presentation/home/adapter/MainAdapter.kt | 63 +++--- .../home/adapter/PopularUserAdapter.kt | 4 +- .../diffcallback/MainItemDiffCallBack.kt | 10 +- .../adapter/diffcallback/UserDiffCallBack.kt | 2 +- .../presentation/teen/TeenListFragment.kt | 9 +- .../res/drawable/bg_btn_like_selector.xml | 5 + app/src/main/res/layout/item_teen.xml | 2 +- 18 files changed, 242 insertions(+), 200 deletions(-) create mode 100644 app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserLikeUseCase.kt create mode 100644 app/src/main/res/drawable/bg_btn_like_selector.xml diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt index 080bdf8c..335835dc 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/response/UserResponse.kt @@ -9,7 +9,8 @@ data class UserResponse( ) data class UserDto( - val profileImage: String, + val id: Int, + val profileImage: String?, val uniqueId: String, val nickName: String, val birthDay: String, diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt index bc1b6a69..eedea874 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt @@ -33,4 +33,11 @@ interface UserService { @GET("/v1/api/{userType}/find-all-by-category/{category}") suspend fun getCategoryUser(@Path("userType") userType: String, @Path("category") category: String, @Query("page") page: Int, @Query("size") size: Int = 10): Response> + + @POST("/v1/api/user/like") + suspend fun postLikeUser(@Query("likedId") likedId: Int): Response> + + @POST("/v1/api/user/like-cancel") + suspend fun postLikeCancelUser(@Query("likedId") likedId: Int): Response> + } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt index 401f0366..1c2acfa7 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt @@ -32,8 +32,9 @@ object UserMapper { } return User( - userImage = profileImage, - userId = uniqueId, + userId = id, + userImage = profileImage?: "", + uniqueId = uniqueId, userName = nickName, userAge = calculateAge(birthDay).toString(), userSchoolName = schoolName, diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt index 1e28cbab..831dc099 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt @@ -48,6 +48,20 @@ class UserRepositoryImpl @Inject constructor( } } + override suspend fun postLikeUser(likedId: Int): Result = + runCatching { + userService.postLikeUser(likedId).mapper { + it.data ?: throw ApiException.Unknown + } + } + + override suspend fun postLikeCancelUser(likedId: Int): Result = + runCatching { + userService.postLikeCancelUser(likedId).mapper { + it.data ?: throw ApiException.Unknown + } + } + override suspend fun fetchUserDetailInfo(id: String): Result = runCatching { // userService.postProfileDetailInfo(id).mapper { profileDetailResponse -> // ProfileDetailMapper.asProfileDetailModel(profileDetailResponse = profileDetailResponse) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt index 94f298fa..5d851cb6 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt @@ -8,6 +8,7 @@ import com.eighteen.eighteenandroid.domain.usecase.GetMyProfileUseCase import com.eighteen.eighteenandroid.domain.usecase.GetUserDetailInfoUseCase import com.eighteen.eighteenandroid.domain.usecase.LoginUseCase import com.eighteen.eighteenandroid.domain.usecase.SignUpUseCase +import com.eighteen.eighteenandroid.domain.usecase.UserLikeUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -44,4 +45,8 @@ object UserUseCaseModule { @Provides @Singleton fun provideGetUserUseCase(repository: UserRepository) = GetUserUseCase(repository = repository) + + @Provides + @Singleton + fun provideUserLikeUseCase(repository: UserRepository) = UserLikeUseCase(repository = repository) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt index cd7e802d..e589e440 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/model/User.kt @@ -1,10 +1,11 @@ package com.eighteen.eighteenandroid.domain.model data class User( + val userId: Int, val userImage: String, - val userId: String, + val uniqueId: String, val userName: String, val userAge: String, val userSchoolName: String, - val likeStatus: Boolean + var likeStatus: Boolean ) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt index 4b2f5b92..3a437b0c 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt @@ -15,4 +15,6 @@ interface UserRepository { suspend fun login(phoneNumber: String): Result suspend fun fetchAllUser(userType: String, page: Int): Result suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result + suspend fun postLikeUser(likedId: Int): Result + suspend fun postLikeCancelUser(likedId: Int): Result } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserLikeUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserLikeUseCase.kt new file mode 100644 index 00000000..fcf22686 --- /dev/null +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/UserLikeUseCase.kt @@ -0,0 +1,13 @@ +package com.eighteen.eighteenandroid.domain.usecase + +import com.eighteen.eighteenandroid.domain.repository.UserRepository +import javax.inject.Inject + +class UserLikeUseCase @Inject constructor(private val repository: UserRepository) { + suspend operator fun invoke(isLike: Boolean, likedId: Int) = + if(isLike) { + repository.postLikeUser(likedId) + } else { + repository.postLikeCancelUser(likedId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/dialog/ReportDialogFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/dialog/ReportDialogFragment.kt index 343e077f..815cddff 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/dialog/ReportDialogFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/dialog/ReportDialogFragment.kt @@ -48,7 +48,7 @@ class ReportDialogFragment : BaseDialogFragment(Dial fun newInstance(user: User): ReportDialogFragment { val bundle = Bundle().apply { - putString(KEY_USER_ID, user.userId) + putString(KEY_USER_ID, user.uniqueId) putString(KEY_USER_NAME, user.userName) } return ReportDialogFragment().apply { arguments = bundle } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 13b404f3..2fa7e44d 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.DisplayMetrics import android.view.View import android.view.animation.AlphaAnimation +import android.widget.ImageButton import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -14,7 +15,6 @@ import androidx.recyclerview.widget.RecyclerView import com.eighteen.eighteenandroid.R import com.eighteen.eighteenandroid.common.enums.Tag import com.eighteen.eighteenandroid.databinding.FragmentMainBinding -import com.eighteen.eighteenandroid.domain.model.AboutTeen import com.eighteen.eighteenandroid.domain.model.Tournament import com.eighteen.eighteenandroid.domain.model.User import com.eighteen.eighteenandroid.presentation.BaseFragment @@ -60,7 +60,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false private var isLoading = false - private var isLastPage = false // 현재 카테고리 private var selectedChip: Chip? = null // 칩 버튼 View @@ -109,98 +108,48 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private fun initMainAdapter() { initMainAdapterListener() - mainAdapter = - MainAdapter(context = requireContext(), listener = mainAdapterListener).apply { - stateRestorationPolicy = - RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY // 현재 스크롤 위치 저장 + mainAdapter = MainAdapter(context = requireContext(), listener = mainAdapterListener).apply { + stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY // 현재 스크롤 위치 저장 } bind { with(rvMain) { adapter = mainAdapter + itemAnimator = null // Item Notify animation 제거 addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) - // 스크롤이 위로 되면 (dy < 0) 버튼 숨기기, 아래로 스크롤 시( dy > 0 ) 버튼 보여주기 - if (dy > 0 && btnScrollTop.visibility == View.GONE) { - btnScrollTop.startAnimation(fadeIn) - btnScrollTop.visibility = View.VISIBLE - } - - val layoutManager = (layoutManager as? LinearLayoutManager) - - val lastVisibleItemPosition = layoutManager?.findLastCompletelyVisibleItemPosition() + val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition() val itemTotalCount = recyclerView.adapter!!.itemCount-1 - if(isLastPage) { - // isLastPage가 true일 때 - if(lastVisibleItemPosition == itemTotalCount) { - // 최하단에 도달했을 때만 60dp - rvMain.post { - rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) - moveToSavedPosition() - } - } else { - // 그 외에는 20dp - binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(20)) - } - } else { - // isLastPage가 false일 때는 항상 20dp - binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(20)) - - if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { - if(_pageNumList.isEmpty()) { - isLastPage = true - binding.rvMain.post { - binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) - moveToSavedPosition() - } - } else { - isLastPage = false - if(!isLoading) { - val page = _pageNumList.random() - viewModel.removePage(page) - viewModel.requestNextPage(category, page) - } + // 스크롤이 끝에 도달했는지 확인 + if (::_pageNumList.isInitialized && !recyclerView.canScrollVertically(1) && lastVisibleItemPosition == itemTotalCount) { + if( _pageNumList.isNotEmpty()) { + if(!isLoading) { + val page = _pageNumList.random() + viewModel.removePage(page) + viewModel.requestNextPage(category, page) } } } + // 스크롤이 위로 되면 (dy < 0) 버튼 숨기기, 아래로 스크롤 시( dy > 0 ) 버튼 보여주기 + if (dy > 0 && btnScrollTop.visibility == View.GONE) { + btnScrollTop.startAnimation(fadeIn) + btnScrollTop.visibility = View.VISIBLE + } + if (!canScrollVertically(-1)) { isTop = true } else { isTop = false - - // 아래로 더 이동할 수 없을 때 -// if(lastVisibleItemPosition != null && lastVisibleItemPosition == itemTotalCount) { -// if(_pageNumList.isEmpty()) { -// isLastPage = true -// -// binding.rvMain.post { -// binding.rvMain.setPadding(0, 0, 0, requireContext().dp2Px(60)) -// moveToSavedPosition() -// } -// } else { -// isLastPage = false -// -// if(!isLoading) { -// // 남은 페이지 유저 목록이 있다면 -// val page = _pageNumList.random() // 남은 페이지 번호 랜덤으로 가져오기 -// viewModel.removePage(page) // 사용한 페이지는 목록에서 제거 -// -// // page 정보 가져오기 -// viewModel.requestNextPage(category, page) -// } -// } -// } } } override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) - val layoutManager = (layoutManager as? LinearLayoutManager) // Log.i("MainScrollStateChanged", "findLastVisible = ${layoutManager?.findLastVisibleItemPosition().toString()}") @@ -217,7 +166,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { - viewModel.pageScrollPosition = getCenterItemPosition(recyclerView) if (isTop && btnScrollTop.isVisible) { btnScrollTop.startAnimation(fadeOut) @@ -238,27 +186,14 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl // Top 버튼 btnScrollTop.throttleClick(viewLifecycleOwner.lifecycleScope) { - appbarLayout.setExpanded(true, true) - rvMain.smoothScrollToPosition(0) + moveToTop() } } + } -// livedata.observe() { - // page1 -> 10개 - - // current - // 전체 리스트를 주진 않고 - -// current + 10 -// } - - // 마지막 아이템을 만나면 - - // 데이터 함수 호출 - - // 라이브데이터 값 바꾸고 - - // 뷰 갱신 + private fun moveToTop() { + binding.appbarLayout.setExpanded(true, true) + binding.rvMain.scrollToPosition(0) } private fun initMainAdapterListener() { @@ -274,9 +209,12 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl /** * 유저 좋아요 클릭 */ - override fun onUserLikeClicks(user: User) { + override fun onUserLikeClicks(likeBtn: ImageButton, user: User) { stopAutoScroll() - // TODO. User Like API 호출 + val likeStatus = likeBtn.isSelected + + // User Like API 호출 + viewModel.requestUserLike(likeStatus.not(), likedId = user.userId) } /** @@ -312,7 +250,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl when(title) { "Teen" -> bottomNavigationView.selectedItemId = R.id.teenMainFragment "채팅" -> bottomNavigationView.selectedItemId = R.id.fragmentChat - "토너먼트" -> {} + "토너먼트" -> bottomNavigationView.selectedItemId = R.id.fragmentRanking "나만의 Teen" -> bottomNavigationView.selectedItemId = R.id.fragmentMyProfile } } @@ -362,10 +300,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl viewModel.popularUserPosition = position } - override fun saveScrollPosition(position: Int) { - viewModel.pageScrollPosition = position - } - override fun startAutoScroll() { isAutoScrolling = true autoScrollJob = viewLifecycleOwner.lifecycleScope.launch { @@ -424,12 +358,18 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl _pageNumList = it } - collectInLifecycle(viewModel.mainItemStateFlow) { + collectInLifecycle(viewModel.mainItemStateFlow) { it -> when(it) { is ModelState.Loading -> { - + isLoading = true + mainAdapter.addLoadingView { lastPosition -> + binding.rvMain.scrollToPosition(lastPosition) + } } is ModelState.Success -> { + isLoading = false + mainAdapter.removeLoadingView() + it.data?.let { mainItems -> mainAdapter.updateView(mainItems) } @@ -443,28 +383,42 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl } } - collectInLifecycle(viewModel.appendStateFlow) { + collectInLifecycle(viewModel.userLikeStateFlow) { when(it) { is ModelState.Loading -> { - isLoading = true - mainAdapter.addLoadingView() { - moveToSavedPosition() - } } is ModelState.Success -> { - isLoading = false + it.data?.let { userId -> + mainAdapter.updateUserLikeStatus(binding.rvMain, userId, isLike = true) + } + } - it.data?.let { newItems -> - mainAdapter.appendItems(newItems) { - // after Notify -> 기존 스크롤 유지 - moveToSavedPosition() - } + is ModelState.Error -> { + + } + + else -> { + //do nothing + } + } + } + + collectInLifecycle(viewModel.userLikeCancelStateFlow) { + when(it) { + is ModelState.Loading -> { + + } + is ModelState.Success -> { + it.data?.let { userId -> + mainAdapter.updateUserLikeStatus(binding.rvMain, userId, isLike = false) } } + is ModelState.Error -> { } + else -> { //do nothing } @@ -500,30 +454,4 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private fun getUserData(tag: Tag) { viewModel.initMain(tag) } - - private fun moveToSavedPosition() { - lifecycleScope.launch { - bind { - // 마지막 스크롤 상태로 돌아오기 - if (viewModel.pageScrollPosition != -1) { - - rvMain.scrollToPosition(viewModel.pageScrollPosition) - - // LayoutManager에서 해당 위치의 아이템을 중앙에 위치시키도록 오프셋 조정 - rvMain.post { - val layoutManager = rvMain.layoutManager as LinearLayoutManager - val viewAtPosition = - layoutManager.findViewByPosition(viewModel.pageScrollPosition) - if (viewAtPosition != null) { - val offset = (rvMain.height - viewAtPosition.height) / 2 - layoutManager.scrollToPositionWithOffset( - viewModel.pageScrollPosition, - offset - ) - } - } - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 50e2de2b..a3e15702 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -14,6 +14,7 @@ import com.eighteen.eighteenandroid.domain.model.MainItem import com.eighteen.eighteenandroid.domain.model.Tournament import com.eighteen.eighteenandroid.domain.usecase.GetUserUseCase import com.eighteen.eighteenandroid.domain.usecase.GetAuthTokenFlowUseCase +import com.eighteen.eighteenandroid.domain.usecase.UserLikeUseCase import com.eighteen.eighteenandroid.presentation.common.ModelState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async @@ -30,39 +31,36 @@ import javax.inject.Inject class MainViewModel @Inject constructor( private val resourceProvider: ResourceProvider, private val getUserUseCase: GetUserUseCase, - private val getAuthTokenFlowUseCase: GetAuthTokenFlowUseCase + private val getAuthTokenFlowUseCase: GetAuthTokenFlowUseCase, + private val userLikeUseCase: UserLikeUseCase ) : ViewModel() { var popularUserPosition = 0 - var pageScrollPosition = 0 /** 메인화면 전체 데이터 */ private val _mainItemStateFlow = MutableStateFlow>>(ModelState.Empty()) val mainItemStateFlow: StateFlow>> get() = _mainItemStateFlow.asStateFlow() - /** 무한 스크롤을 위한 새로 불러온 데이터 */ - private val _appendStateFlow = MutableStateFlow>>(ModelState.Empty()) - val appendStateFlow: StateFlow>> - get() = _appendStateFlow.asStateFlow() - /** 남은 페이지 목록 */ private val _pageNumList = MutableLiveData>() val pageNumList: LiveData> get() = _pageNumList + /** 좋아요 */ + private val _userLikeStateFlow = MutableStateFlow>(ModelState.Empty()) + val userLikeStateFlow: StateFlow> + get() = _userLikeStateFlow.asStateFlow() + + /** 좋아요 취소 */ + private val _userLikeCancelStateFlow = MutableStateFlow>(ModelState.Empty()) + val userLikeCancelStateFlow: StateFlow> + get() = _userLikeCancelStateFlow.asStateFlow() + init { initMain(Tag.ALL) } - fun removePage(page: Int) { - val pages = _pageNumList.value - pages?.let { - it.remove(page) - _pageNumList.postValue(it) - } - } - fun initMain(category: Tag) { val items = mutableListOf() @@ -149,10 +147,10 @@ class MainViewModel @Inject constructor( /** 또 다른 Teen 무한 스크롤, 다음 페이지 유저 정보 가져오기 */ fun requestNextPage(category: Tag, page: Int) { viewModelScope.launch { - val items = mutableListOf() + val items = _mainItemStateFlow.value.data?.toMutableList() // 로딩 시작 - _appendStateFlow.value = ModelState.Loading() + _mainItemStateFlow.value = ModelState.Loading() delay(1000) // 로그인 상태 확인 @@ -169,17 +167,27 @@ class MainViewModel @Inject constructor( fetchAnotherUser(category, USER_TYPE_SIGNIN, page).await() } - anotherTeenInitialState.onSuccess { - it.users.forEach { user -> - items.add( - MainItem.UserView(user) - ) + anotherTeenInitialState + .onSuccess { + it.users.forEach { user -> + items?.add( + MainItem.UserView(user) + ) + } + + _mainItemStateFlow.value = ModelState.Success(items) + } + .onFailure { e -> + _mainItemStateFlow.value = ModelState.Error(throwable = e) } + } + } - _appendStateFlow.value = ModelState.Success(items) - }.onFailure { e -> - _appendStateFlow.value = ModelState.Error(throwable = e) - } + fun removePage(page: Int) { + val pages = _pageNumList.value + pages?.let { + it.remove(page) + _pageNumList.postValue(it) } } @@ -187,6 +195,41 @@ class MainViewModel @Inject constructor( getUserUseCase.invoke(category.name, userType, page) } + fun requestUserLike(isLike: Boolean, likedId: Int) { + viewModelScope.launch { + if(isLike) { // 좋아요 + _userLikeStateFlow.value = ModelState.Loading() + + postLikeUser(likedId).await() + .onSuccess { + _userLikeStateFlow.value = ModelState.Success(likedId) + } + .onFailure { + _userLikeStateFlow.value = ModelState.Error(throwable = it) + } + + } else { // 좋아요 취소 + _userLikeCancelStateFlow.value = ModelState.Loading() + + postLikeCancelUser(likedId).await() + .onSuccess { + _userLikeCancelStateFlow.value = ModelState.Success(likedId) + } + .onFailure { + _userLikeCancelStateFlow.value = ModelState.Error(throwable = it) + } + } + } + } + + private fun postLikeUser(likedId: Int) = viewModelScope.async { + userLikeUseCase.invoke(true, likedId) + } + + private fun postLikeCancelUser(likedId: Int) = viewModelScope.async { + userLikeUseCase.invoke(false, likedId) + } + companion object { const val TAG = "MainViewModel" } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index f0e743b9..ff2b78f3 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -4,6 +4,7 @@ import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageButton import androidx.core.view.doOnLayout import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.PagerSnapHelper @@ -22,7 +23,6 @@ import com.eighteen.eighteenandroid.databinding.ItemTeenBinding import com.eighteen.eighteenandroid.domain.model.MainItem import com.eighteen.eighteenandroid.domain.model.Tournament import com.eighteen.eighteenandroid.domain.model.User -import com.eighteen.eighteenandroid.presentation.common.dp2Px import com.eighteen.eighteenandroid.presentation.home.adapter.diffcallback.MainItemDiffCallBack interface MainAdapterListener { @@ -30,7 +30,7 @@ interface MainAdapterListener { fun onUserClicks(user: User) /** 유저 좋아요 클릭 */ - fun onUserLikeClicks(user: User) + fun onUserLikeClicks(likeBtn: ImageButton, user: User) /** 유저 채팅 클릭 */ fun onUserChatClicks(user: User) @@ -53,9 +53,6 @@ interface MainAdapterListener { /** 이전에 보여진 유저 포지션 저장*/ fun saveUserPosition(position: Int) - /** 현재 메인화면 스크롤 포지션 저장*/ - fun saveScrollPosition(position: Int) - fun startAutoScroll() fun stopAutoScroll() @@ -278,16 +275,16 @@ class MainAdapter( tvSchool.text = user.userSchoolName tvName.text = userName Glide.with(context).load(user.userImage).into(imgTodayTeen) // 프로필 이미지 + btnLike.isSelected = user.likeStatus // 좋아요 버튼 Selector imgTodayTeen.setOnClickListener { listener.onUserClicks(user) - listener.saveScrollPosition(position) // 현재 메인화면 스크롤 position 저장 } btnChat.setOnClickListener { listener.onUserChatClicks(user) } btnLike.setOnClickListener { - listener.onUserLikeClicks(user) + listener.onUserLikeClicks(btnLike, user) } btnSetting.setOnClickListener { listener.onUserMoreClicks(btnSetting, user) @@ -295,6 +292,10 @@ class MainAdapter( } } } + + fun updateLikeBtn(isLike: Boolean) { + binding.btnLike.isSelected = isLike + } } class AboutTeenListViewHolder( @@ -350,34 +351,44 @@ class MainAdapter( submitList(list) } - // 기존 데이터를 보존하면서 새 데이터만 추가하는 메소드 - fun appendItems(newItems: List, afterNotify: () -> Unit) { + fun addLoadingView(scrollToPosition: (Int) -> Unit) { val currentList = currentList.toMutableList() - currentList.remove(MainItem.LoadingView) - notifyItemRangeRemoved(currentList.size, 1) - - val startPosition = currentList.size - listener.saveScrollPosition(startPosition - 1) // 기존 스크롤 위치 저장 - currentList.addAll(newItems) + currentList.add(MainItem.LoadingView) submitList(currentList) { - // submitList 콜백에서 새로운 아이템에 대해서만 notify - notifyItemRangeInserted(startPosition, newItems.size) - afterNotify() + scrollToPosition(currentList.size - 1) } } - fun addLoadingView( afterNotify: () -> Unit ) { + fun removeLoadingView() { val currentList = currentList.toMutableList() - val startPosition = currentList.size + if(currentList.lastIndex > 0) currentList.removeAt(currentList.lastIndex) - currentList.add(MainItem.LoadingView) - listener.saveScrollPosition(startPosition - 1) // 기존 스크롤 위치 저장 + submitList(currentList) + } - submitList(currentList) { - // submitList 콜백에서 새로운 아이템에 대해서만 notify - notifyItemRangeInserted(startPosition, 1) - afterNotify() + fun updateUserLikeStatus(recyclerView: RecyclerView, userId: Int, isLike: Boolean) { + // 전체 아이템 중에서 해당 userId를 가진 유저 아이템 찾기 + val position = currentList.indexOfFirst { item -> + when (item) { + is MainItem.UserView -> item.user.userId == userId + else -> false + } + } + + if (position != -1) { + // 해당 위치의 ViewHolder 찾기 + val viewHolder = recyclerView.findViewHolderForAdapterPosition(position) as? CommonViewHolder.UserViewHolder + + // ViewHolder가 현재 화면에 보이는 경우 직접 업데이트 + viewHolder?.updateLikeBtn(isLike) + + // 데이터도 업데이트 + val currentItem = currentList[position] as? MainItem.UserView + currentItem?.user?.likeStatus = isLike + + // 해당 포지션만 갱신 + notifyItemChanged(position) } } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/PopularUserAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/PopularUserAdapter.kt index 4eb0f9f9..a3959465 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/PopularUserAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/PopularUserAdapter.kt @@ -9,7 +9,6 @@ import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.eighteen.eighteenandroid.databinding.ItemPopularTeenBinding import com.eighteen.eighteenandroid.domain.model.User -import com.eighteen.eighteenandroid.presentation.common.dp2Px import com.eighteen.eighteenandroid.presentation.common.getScreenWidth import com.eighteen.eighteenandroid.presentation.home.adapter.diffcallback.UserDiffCallBack import kotlin.math.roundToInt @@ -53,6 +52,7 @@ class PopularUserAdapter( Glide.with(context).load(user.userImage).into(imgTodayTeen) tvName.text = "${user.userName}, ${user.userAge}" tvSchool.text = user.userSchoolName + btnLike.isSelected = user.likeStatus // 좋아요 상태 imgTodayTeen.setOnClickListener { mainAdapterListener.onUserClicks(user) @@ -63,7 +63,7 @@ class PopularUserAdapter( } btnLike.setOnClickListener { - mainAdapterListener.onUserLikeClicks(user) + mainAdapterListener.onUserLikeClicks(btnLike, user) } btnSetting.setOnClickListener { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/MainItemDiffCallBack.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/MainItemDiffCallBack.kt index bd8735a3..3253ac43 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/MainItemDiffCallBack.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/MainItemDiffCallBack.kt @@ -19,7 +19,7 @@ class MainItemDiffCallBack: DiffUtil.ItemCallback() { } oldItem is MainItem.DividerView && newItem is MainItem.DividerView -> { - false + true } oldItem is MainItem.AboutTeenListView && newItem is MainItem.AboutTeenListView -> { @@ -30,6 +30,14 @@ class MainItemDiffCallBack: DiffUtil.ItemCallback() { oldItem.tournamentList == newItem.tournamentList } + oldItem is MainItem.UserView && newItem is MainItem.UserView -> { + oldItem.user.userId == newItem.user.userId || oldItem.user.uniqueId == newItem.user.uniqueId + } + + oldItem is MainItem.LoadingView && newItem is MainItem.LoadingView -> { + true + } + else -> false } } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/UserDiffCallBack.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/UserDiffCallBack.kt index 9e6471f5..3414da6d 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/UserDiffCallBack.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/diffcallback/UserDiffCallBack.kt @@ -5,7 +5,7 @@ import com.eighteen.eighteenandroid.domain.model.User class UserDiffCallBack() : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean { - return oldItem.userId == newItem.userId + return oldItem.uniqueId == newItem.uniqueId || oldItem.userId == newItem.userId } override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt index d865dbd7..c03c2267 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/teen/TeenListFragment.kt @@ -58,24 +58,27 @@ class TeenListFragment: BaseFragment(FragmentTeenListBi adapter.submitList( listOf( User( + userId = 1, userImage = "https://image.blip.kr/v1/file/021ec61ff1c9936943383b84236a0e69", - userId = "1", + uniqueId = "1", userName = "김 에스더", userAge = "16", userSchoolName = "서울 중학교", likeStatus = true ), User( + userId = 2, userImage = "https://cdn.newsculture.press/news/photo/202308/529742_657577_5726.jpg", - userId = "2", + uniqueId = "2", userName = "김 에스더", userAge = "16", userSchoolName = "부천 중학교", likeStatus = false ), User( + userId = 3, userImage = "https://mblogthumb-phinf.pstatic.net/MjAyMTEwMzFfMTY1/MDAxNjM1NjUzMTI2NjI3.xXYQteLLoWLKcR9YnXS0Hk_y-DInauMzF25g7FxlcScg.2Y-neBBMVoP2IhcwzX2Zy2HB2d8EnM_cY76FVLuk_1Yg.JPEG.ssun2415/IMG_4148.jpg?type=w800", - userId = "3", + uniqueId = "3", userName = "김 에스더", userAge = "16", userSchoolName = "인천 중학교", diff --git a/app/src/main/res/drawable/bg_btn_like_selector.xml b/app/src/main/res/drawable/bg_btn_like_selector.xml new file mode 100644 index 00000000..a3df7e5f --- /dev/null +++ b/app/src/main/res/drawable/bg_btn_like_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_teen.xml b/app/src/main/res/layout/item_teen.xml index 8bbcfd1c..eb709b67 100644 --- a/app/src/main/res/layout/item_teen.xml +++ b/app/src/main/res/layout/item_teen.xml @@ -75,7 +75,7 @@ android:layout_height="48dp" android:layout_marginEnd="16dp" android:background="@drawable/bg_oval_main_color" - android:src="@drawable/ic_empty_heart" + android:src="@drawable/bg_btn_like_selector" app:layout_constraintBottom_toTopOf="@id/btn_setting" app:layout_constraintEnd_toEndOf="parent" /> From 62eb6d514e445312b79f554483030ff97a9ca7d9 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Wed, 8 Jan 2025 15:36:52 +0900 Subject: [PATCH 08/15] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20=ED=86=B5=ED=95=A9=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/remote/service/UserService.kt | 5 +---- .../eighteenandroid/data/repository/UserRepositoryImpl.kt | 7 ------- .../eighteenandroid/domain/repository/UserRepository.kt | 1 - .../eighteenandroid/domain/usecase/GetUserUseCase.kt | 6 +----- 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt index 67b07e15..d4e9a51b 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt @@ -30,10 +30,7 @@ interface UserService { @POST("/v1/api/user/sign-in") suspend fun postLogin(@Query("phoneNumber") phoneNumber: String): Response> - @GET("/v1/api/{userType}/find-all") - suspend fun getAllUser(@Path("userType") userType: String, @Query("page") page: Int, @Query("size") size: Int = 10): Response> - - @GET("/v1/api/{userType}/find-all-by-category/{category}") + @GET("/v1/api/{userType}/find-all/{category}") suspend fun getCategoryUser(@Path("userType") userType: String, @Path("category") category: String, @Query("page") page: Int, @Query("size") size: Int = 10): Response> @POST("/v1/api/user/like") diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt index 999e90e1..172fab1e 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt @@ -34,13 +34,6 @@ class UserRepositoryImpl @Inject constructor( private val userService: UserService, private val preferenceDatastore: DataStore ) : UserRepository { - override suspend fun fetchAllUser(userType: String, page: Int): Result = - runCatching { - userService.getAllUser(userType, page).mapper { - it.data?.toUserUseCaseModel() ?: UserUseCaseModel(emptyList(), 0) - } - } - override suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result = runCatching { userService.getCategoryUser(userType, category, page).mapper { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt index 3a437b0c..7ba87b1f 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt @@ -13,7 +13,6 @@ interface UserRepository { fun getTokenFlow(): Flow suspend fun saveToken(authToken: AuthToken) suspend fun login(phoneNumber: String): Result - suspend fun fetchAllUser(userType: String, page: Int): Result suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result suspend fun postLikeUser(likedId: Int): Result suspend fun postLikeCancelUser(likedId: Int): Result diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt index 9ab7baa8..e920cecd 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt @@ -5,9 +5,5 @@ import com.eighteen.eighteenandroid.domain.repository.UserRepository import javax.inject.Inject class GetUserUseCase @Inject constructor(private val repository: UserRepository) { - suspend operator fun invoke(category: String, userType: String, page: Int) = if(category == Tag.ALL.name) { - repository.fetchAllUser(userType, page) - } else { - repository.fetchCategoryUser(userType, category, page) - } + suspend operator fun invoke(category: String, userType: String, page: Int) = repository.fetchCategoryUser(userType, category, page) } \ No newline at end of file From b60a5bc870e0cf1e681c32a3eafc173908145c82 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Wed, 8 Jan 2025 15:37:27 +0900 Subject: [PATCH 09/15] =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20drawable=20=EC=88=98=EC=A0=95=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/drawable/bg_btn_like_selector.xml | 4 ++-- app/src/main/res/drawable/bg_empty_heart.xml | 7 +++++++ app/src/main/res/drawable/bg_full_heart.xml | 7 +++++++ app/src/main/res/layout/item_popular_teen.xml | 3 +-- app/src/main/res/layout/item_teen.xml | 3 +-- 5 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable/bg_empty_heart.xml create mode 100644 app/src/main/res/drawable/bg_full_heart.xml diff --git a/app/src/main/res/drawable/bg_btn_like_selector.xml b/app/src/main/res/drawable/bg_btn_like_selector.xml index a3df7e5f..f676e322 100644 --- a/app/src/main/res/drawable/bg_btn_like_selector.xml +++ b/app/src/main/res/drawable/bg_btn_like_selector.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_empty_heart.xml b/app/src/main/res/drawable/bg_empty_heart.xml new file mode 100644 index 00000000..29023e4d --- /dev/null +++ b/app/src/main/res/drawable/bg_empty_heart.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_full_heart.xml b/app/src/main/res/drawable/bg_full_heart.xml new file mode 100644 index 00000000..8cf08322 --- /dev/null +++ b/app/src/main/res/drawable/bg_full_heart.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_popular_teen.xml b/app/src/main/res/layout/item_popular_teen.xml index 2f7a5e8f..133915c4 100644 --- a/app/src/main/res/layout/item_popular_teen.xml +++ b/app/src/main/res/layout/item_popular_teen.xml @@ -66,8 +66,7 @@ android:layout_width="48dp" android:layout_height="48dp" android:layout_marginEnd="16dp" - android:background="@drawable/bg_oval_main_color" - android:src="@drawable/ic_empty_heart" + android:background="@drawable/bg_btn_like_selector" app:layout_constraintBottom_toTopOf="@id/btn_setting" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/item_teen.xml b/app/src/main/res/layout/item_teen.xml index eb709b67..e8b7a942 100644 --- a/app/src/main/res/layout/item_teen.xml +++ b/app/src/main/res/layout/item_teen.xml @@ -74,8 +74,7 @@ android:layout_width="48dp" android:layout_height="48dp" android:layout_marginEnd="16dp" - android:background="@drawable/bg_oval_main_color" - android:src="@drawable/bg_btn_like_selector" + android:background="@drawable/bg_btn_like_selector" app:layout_constraintBottom_toTopOf="@id/btn_setting" app:layout_constraintEnd_toEndOf="parent" /> From 745d2d4935be15421f2e9bbfa0cb953d4650769d Mon Sep 17 00:00:00 2001 From: yys7517 Date: Mon, 20 Jan 2025 11:14:43 +0900 Subject: [PATCH 10/15] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=B4=88=EA=B8=B0=ED=99=94,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/MyViewModel.kt | 13 +- .../auth/signup/SignUpFragment.kt | 3 +- .../auth/signup/SignUpViewModel.kt | 6 +- .../signup/SignUpViewModelContentInterface.kt | 2 +- .../SignUpEnterAuthCodeFragment.kt | 3 +- .../presentation/home/MainFragment.kt | 14 +- .../presentation/home/MainViewModel.kt | 157 ++++++++---------- 7 files changed, 101 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/MyViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/MyViewModel.kt index 1721599a..69ed0e49 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/MyViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/MyViewModel.kt @@ -1,5 +1,7 @@ package com.eighteen.eighteenandroid.presentation +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.eighteen.eighteenandroid.domain.model.AuthToken @@ -34,10 +36,12 @@ class MyViewModel @Inject constructor( private val _myProfileStateFlow = MutableStateFlow>(ModelState.Empty()) val myProfileStateFlow = _myProfileStateFlow.asStateFlow() - private val _editProfileEventStateFlow = - MutableStateFlow>>(ModelState.Empty()) + private val _editProfileEventStateFlow = MutableStateFlow>>(ModelState.Empty()) val editProfileEventStateFlow = _editProfileEventStateFlow.asStateFlow() + private val _userSignEventLiveData = MutableLiveData>() + val userSignEventLiveData: LiveData> = _userSignEventLiveData + private var myProfileJob: Job? = null private var editMyProfileJob: Job? = null @@ -50,11 +54,12 @@ class MyViewModel @Inject constructor( requestMyProfile() } - fun completeLogin(authToken: AuthToken) { + fun completeLogin(authToken: AuthToken?) { if (myProfileJob?.isCompleted == false) return myProfileJob = viewModelScope.launch { _myProfileStateFlow.value = ModelState.Loading() - saveAuthTokenUseCase.invoke(authToken) + authToken?.let { saveAuthTokenUseCase.invoke(it) } + _userSignEventLiveData.value = Event(authToken) getMyProfileUseCase.invoke().onSuccess { _myProfileStateFlow.value = ModelState.Success(it) }.onFailure { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpFragment.kt index f8047174..25510ec0 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpFragment.kt @@ -184,6 +184,7 @@ class SignUpFragment : BaseFragment(FragmentSignUpBinding private fun initSignUpResultStateFlow() { collectInLifecycle(signUpViewModel.signUpResultStateFlow) { when (it) { + is ModelState.Loading -> {} is ModelState.Success -> { it.data?.let { authToken -> myViewModel.completeLogin(authToken = authToken) @@ -191,7 +192,7 @@ class SignUpFragment : BaseFragment(FragmentSignUpBinding findNavController().navigate(R.id.action_fragmentSignUp_to_fragmentSignUpCompleted) } else -> { - //do nothing + myViewModel.completeLogin(null) } } } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModel.kt index 1647ff09..eba21b13 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModel.kt @@ -69,8 +69,8 @@ class SignUpViewModel @Inject constructor( override val pageClearEvent: StateFlow> = _pageClearEventStateFlow.asStateFlow() - private val _requestLoginEventLiveData = MutableLiveData>() - val requestLoginEventLiveData: LiveData> = _requestLoginEventLiveData + private val _requestLoginEventLiveData = MutableLiveData>() + val requestLoginEventLiveData: LiveData> = _requestLoginEventLiveData override var phoneNumber: String = "" override var id: String = "" @@ -191,7 +191,7 @@ class SignUpViewModel @Inject constructor( } } - override fun requestLogin(authToken: AuthToken) { + override fun requestLogin(authToken: AuthToken?) { viewModelScope.launch { _requestLoginEventLiveData.value = Event(authToken) } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModelContentInterface.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModelContentInterface.kt index 6f5a802d..4c14ef75 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModelContentInterface.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/SignUpViewModelContentInterface.kt @@ -34,6 +34,6 @@ interface SignUpViewModelContentInterface { fun setPageClearEvent(page: SignUpPage) fun removeMedia(position: Int) fun setMainMedia(position: Int) - fun requestLogin(authToken: AuthToken) + fun requestLogin(authToken: AuthToken?) fun sendSignUpStatusEvent(event: SignUpStatusEvent) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/enterauthcode/SignUpEnterAuthCodeFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/enterauthcode/SignUpEnterAuthCodeFragment.kt index c7cfae1e..c7e23a0a 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/enterauthcode/SignUpEnterAuthCodeFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/enterauthcode/SignUpEnterAuthCodeFragment.kt @@ -193,7 +193,8 @@ class SignUpEnterAuthCodeFragment : if (data is ConfirmResultModel.LoginSuccess) { signUpViewModelContentInterface.requestLogin(authToken = data.authToken) } else { - //TODO 로그인 실패 + // 로그인 실패 + signUpViewModelContentInterface.requestLogin(authToken = null) } } } diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 2fa7e44d..e4f966d8 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.animation.AlphaAnimation import android.widget.ImageButton import androidx.core.view.isVisible +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -18,6 +19,7 @@ import com.eighteen.eighteenandroid.databinding.FragmentMainBinding import com.eighteen.eighteenandroid.domain.model.Tournament import com.eighteen.eighteenandroid.domain.model.User import com.eighteen.eighteenandroid.presentation.BaseFragment +import com.eighteen.eighteenandroid.presentation.MyViewModel import com.eighteen.eighteenandroid.presentation.common.ModelState import com.eighteen.eighteenandroid.presentation.common.collectInLifecycle import com.eighteen.eighteenandroid.presentation.common.createChip @@ -45,6 +47,7 @@ import kotlinx.coroutines.launch @AndroidEntryPoint class MainFragment : BaseFragment(FragmentMainBinding::inflate) { private val viewModel by viewModels() + private val myViewModel by activityViewModels() private lateinit var mainAdapter: MainAdapter @@ -213,8 +216,10 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl stopAutoScroll() val likeStatus = likeBtn.isSelected - // User Like API 호출 - viewModel.requestUserLike(likeStatus.not(), likedId = user.userId) + requestWithRequiredLogin { + // User Like API 호출 + viewModel.requestUserLike(likeStatus.not(), likedId = user.userId) + } } /** @@ -358,6 +363,10 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl _pageNumList = it } + myViewModel.userSignEventLiveData.observe(viewLifecycleOwner) { + getUserData(category) + } + collectInLifecycle(viewModel.mainItemStateFlow) { it -> when(it) { is ModelState.Loading -> { @@ -433,6 +442,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl if (tag == Tag.ALL) { // 화면 최초 진입 시 전체 태그가 클릭된 상태여야함 chip.setTagStyle(isBlackBackground = true) selectedChip = chip + category = tag } chip.setOnClickListener { _ -> selectedChip?.setTagStyle(isBlackBackground = false) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index a3e15702..6da49f29 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -20,10 +20,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import javax.inject.Inject @@ -38,7 +37,8 @@ class MainViewModel @Inject constructor( var popularUserPosition = 0 /** 메인화면 전체 데이터 */ - private val _mainItemStateFlow = MutableStateFlow>>(ModelState.Empty()) + private val _mainItemStateFlow = + MutableStateFlow>>(ModelState.Empty()) val mainItemStateFlow: StateFlow>> get() = _mainItemStateFlow.asStateFlow() @@ -61,110 +61,96 @@ class MainViewModel @Inject constructor( initMain(Tag.ALL) } - fun initMain(category: Tag) { + fun initMain(category: Tag) = viewModelScope.launch { val items = mutableListOf() - - viewModelScope.launch { - _mainItemStateFlow.value = ModelState.Loading() - - // TODO. val popularTeenInitialState (인기 Teen) - - items.addAll( - listOf( - MainItem.HeaderView(resourceProvider.getString(R.string.main_today_teen)), - MainItem.UserListView( - emptyList() - ), // TODO. 인기 Teen - MainItem.DividerView, - MainItem.HeaderView(resourceProvider.getString(R.string.main_about_teen)), - MainItem.AboutTeenListView( - listOf( - AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), - AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), - AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), - AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") - ) - ), // About Teen List - MainItem.DividerView, - MainItem.HeaderWithMoreView(resourceProvider.getString(R.string.main_tournament_in_progress)), - MainItem.TournamentListView( - listOf( - Tournament.Exercise, - Tournament.Study - ) - ), // TODO. Tournament List - MainItem.DividerView, - MainItem.HeaderView(resourceProvider.getString(R.string.main_another_teen)) - ) - ) - - // 로그인 상태 확인 - val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( - viewModelScope, - SharingStarted.Eagerly, null + _mainItemStateFlow.value = ModelState.Loading() + + // TODO. val popularTeenInitialState (인기 Teen) + + items.addAll( + listOf( + MainItem.HeaderView(resourceProvider.getString(R.string.main_today_teen)), + MainItem.UserListView( + emptyList() + ), // TODO. 인기 Teen + MainItem.DividerView, + MainItem.HeaderView(resourceProvider.getString(R.string.main_about_teen)), + MainItem.AboutTeenListView( + listOf( + AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), + AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), + AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), + AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") + ) + ), // About Teen List + MainItem.DividerView, + MainItem.HeaderWithMoreView(resourceProvider.getString(R.string.main_tournament_in_progress)), + MainItem.TournamentListView( + listOf( + Tournament.Exercise, + Tournament.Study + ) + ), // TODO. Tournament List + MainItem.DividerView, + MainItem.HeaderView(resourceProvider.getString(R.string.main_another_teen)) ) + ) + + // AuthToken 여부 확인 후 Fetch + val authToken = getAuthTokenFlowUseCase.invoke().firstOrNull() + val anotherTeenInitialState = if (authToken != null) { + fetchUser(category, USER_TYPE_SIGNIN, 0) + } else { + fetchUser(category, USER_TYPE_GUEST, 0) + } - val anotherTeenInitialState = if(authTokenStateFlow.value == null) { - // 게스트 - fetchAnotherUser(category, USER_TYPE_GUEST, 0).await() - } else { - // 유저 - fetchAnotherUser(category, USER_TYPE_SIGNIN, 0).await() - } - - anotherTeenInitialState.onSuccess { - if(it.totalPageCount > 1) { - val pages = mutableListOf() - - // 첫 번째 페이지(0)를 제외한 페이지 목록 - for (page in 1 until it.totalPageCount) { - pages.add(page) - } + anotherTeenInitialState.onSuccess { + if (it.totalPageCount > 1) { + val pages = mutableListOf() - _pageNumList.postValue(pages) + // 첫 번째 페이지(0)를 제외한 페이지 목록 + for (page in 1 until it.totalPageCount) { + pages.add(page) } - it.users.forEach { user -> - items.add( - MainItem.UserView(user) - ) - } + _pageNumList.postValue(pages) + } - _mainItemStateFlow.value = ModelState.Success(items) - }.onFailure { e -> - _mainItemStateFlow.value = ModelState.Error(throwable = e) + it.users.forEach { user -> + items.add( + MainItem.UserView(user) + ) } + _mainItemStateFlow.value = ModelState.Success(items) + }.onFailure { e -> + _mainItemStateFlow.value = ModelState.Error(throwable = e) + } + // if( userDataState is ModelState.Success && aboutTeenDataState is ModelState.Success ) { // // updateMain // } else { // // 에러화면 // } - } } + /** 또 다른 Teen 무한 스크롤, 다음 페이지 유저 정보 가져오기 */ fun requestNextPage(category: Tag, page: Int) { viewModelScope.launch { - val items = _mainItemStateFlow.value.data?.toMutableList() + val items = _mainItemStateFlow.value.data?.toMutableList() // 기존 값 // 로딩 시작 _mainItemStateFlow.value = ModelState.Loading() delay(1000) - // 로그인 상태 확인 - val authTokenStateFlow = getAuthTokenFlowUseCase.invoke().stateIn( - viewModelScope, - SharingStarted.Eagerly, null - ) - - val anotherTeenInitialState = if(authTokenStateFlow.value == null) { - // 게스트 - fetchAnotherUser(category, USER_TYPE_GUEST, page).await() + // AuthToken 여부 확인 후 Fetch + val authToken = getAuthTokenFlowUseCase.invoke().firstOrNull() + val anotherTeenInitialState = if (authToken != null) { + fetchUser(category, USER_TYPE_SIGNIN, page) } else { - // 유저 - fetchAnotherUser(category, USER_TYPE_SIGNIN, page).await() + fetchUser(category, USER_TYPE_GUEST, page) } anotherTeenInitialState @@ -191,13 +177,14 @@ class MainViewModel @Inject constructor( } } - private fun fetchAnotherUser(category: Tag, userType: String, page: Int) = viewModelScope.async { - getUserUseCase.invoke(category.name, userType, page) - } + private suspend fun fetchUser(category: Tag, userType: String, page: Int) = + viewModelScope.async { + getUserUseCase.invoke(category.name, userType, page) + }.await() fun requestUserLike(isLike: Boolean, likedId: Int) { viewModelScope.launch { - if(isLike) { // 좋아요 + if (isLike) { // 좋아요 _userLikeStateFlow.value = ModelState.Loading() postLikeUser(likedId).await() From 1a186dfe38b713ca0a0b51d1ab41337508526d4c Mon Sep 17 00:00:00 2001 From: yys7517 Date: Mon, 20 Jan 2025 17:19:01 +0900 Subject: [PATCH 11/15] =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=B5=9C=EC=83=81=EB=8B=A8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/MainFragment.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index e4f966d8..e8333776 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -63,6 +63,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl private var autoScrollJob: Job? = null private var isAutoScrolling = false private var isLoading = false + private var isRequestNextPage = false // 현재 카테고리 private var selectedChip: Chip? = null // 칩 버튼 View @@ -131,6 +132,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl if (::_pageNumList.isInitialized && !recyclerView.canScrollVertically(1) && lastVisibleItemPosition == itemTotalCount) { if( _pageNumList.isNotEmpty()) { if(!isLoading) { + isRequestNextPage = true val page = _pageNumList.random() viewModel.removePage(page) viewModel.requestNextPage(category, page) @@ -382,12 +384,14 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl it.data?.let { mainItems -> mainAdapter.updateView(mainItems) } - } - is ModelState.Error -> { + if(isRequestNextPage.not()) { + moveToTop() + } } else ->{ - //do nothing + // Error + mainAdapter.removeLoadingView() } } } @@ -445,6 +449,8 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl category = tag } chip.setOnClickListener { _ -> + isRequestNextPage = false + selectedChip?.setTagStyle(isBlackBackground = false) chip.setTagStyle(isBlackBackground = true) selectedChip = chip From 1440a2adf2b7c7665c36ad8bb32516cda98beb1c Mon Sep 17 00:00:00 2001 From: yys7517 Date: Mon, 3 Feb 2025 16:49:04 +0900 Subject: [PATCH 12/15] =?UTF-8?q?=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=9D=B8=EA=B8=B0=20Teen=20API=20=EC=97=B0=EA=B2=B0=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eighteenandroid/common/enums/Tag.kt | 2 +- .../datasource/remote/service/UserService.kt | 3 + .../eighteenandroid/data/mapper/UserMapper.kt | 2 +- .../data/repository/UserRepositoryImpl.kt | 10 +- .../domain/di/UserUseCaseModule.kt | 9 +- .../domain/repository/UserRepository.kt | 4 +- ...serUseCase.kt => GetAnotherUserUseCase.kt} | 5 +- .../domain/usecase/GetPopularUserUseCase.kt | 8 ++ .../selecttag/SignUpSelectTagViewModel.kt | 2 +- .../presentation/home/MainFragment.kt | 15 ++- .../presentation/home/MainViewModel.kt | 126 ++++++++++-------- .../presentation/home/adapter/MainAdapter.kt | 4 + 12 files changed, 123 insertions(+), 67 deletions(-) rename app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/{GetUserUseCase.kt => GetAnotherUserUseCase.kt} (50%) create mode 100644 app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetPopularUserUseCase.kt diff --git a/app/src/main/java/com/eighteen/eighteenandroid/common/enums/Tag.kt b/app/src/main/java/com/eighteen/eighteenandroid/common/enums/Tag.kt index ed52eb25..8f1138a9 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/common/enums/Tag.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/common/enums/Tag.kt @@ -3,7 +3,7 @@ package com.eighteen.eighteenandroid.common.enums enum class Tag(val strValue: String) { ALL("전체"), BEAUTY("뷰티"), - EXERCISE("운동"), + SPORT("운동"), STUDY("공부"), ART("예술"), GAME("게임"), diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt index d4e9a51b..50d289cf 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/datasource/remote/service/UserService.kt @@ -3,6 +3,7 @@ package com.eighteen.eighteenandroid.data.datasource.remote.service import com.eighteen.eighteenandroid.data.datasource.remote.request.SignUpRequest import com.eighteen.eighteenandroid.data.datasource.remote.response.ApiResult import com.eighteen.eighteenandroid.data.datasource.remote.response.ProfileDetailResponse +import com.eighteen.eighteenandroid.data.datasource.remote.response.UserDto import com.eighteen.eighteenandroid.data.datasource.remote.response.UserResponse import retrofit2.Response import retrofit2.http.Body @@ -39,4 +40,6 @@ interface UserService { @POST("/v1/api/user/like-cancel") suspend fun postLikeCancelUser(@Query("likedId") likedId: Int): Response> + @GET("/v1/api/teen/{userType}/famous/{category}") + suspend fun getPopularUser(@Path("userType") userType: String, @Path("category") category: String): Response>> } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt index 1c2acfa7..f634dab8 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/mapper/UserMapper.kt @@ -9,7 +9,7 @@ import java.time.Period import java.time.format.DateTimeFormatter object UserMapper { - private fun UserDto.toUser(): User { + fun UserDto.toUser(): User { fun calculateAge(birthDay: String): Int { // 날짜 포맷터 생성 val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") diff --git a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt index 172fab1e..a93f48bf 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/data/repository/UserRepositoryImpl.kt @@ -12,6 +12,7 @@ import com.eighteen.eighteenandroid.data.datasource.remote.request.SchoolRequest import com.eighteen.eighteenandroid.data.datasource.remote.request.SignUpRequest import com.eighteen.eighteenandroid.data.datasource.remote.service.UserService import com.eighteen.eighteenandroid.data.mapper.ApiException +import com.eighteen.eighteenandroid.data.mapper.UserMapper.toUser import com.eighteen.eighteenandroid.data.mapper.UserMapper.toUserUseCaseModel import com.eighteen.eighteenandroid.data.mapper.mapper import com.eighteen.eighteenandroid.domain.model.AuthToken @@ -34,7 +35,7 @@ class UserRepositoryImpl @Inject constructor( private val userService: UserService, private val preferenceDatastore: DataStore ) : UserRepository { - override suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result = + override suspend fun getAnotherUser(userType: String, category: String, page: Int): Result = runCatching { userService.getCategoryUser(userType, category, page).mapper { it.data?.toUserUseCaseModel() ?: UserUseCaseModel(emptyList(), 0) @@ -55,6 +56,13 @@ class UserRepositoryImpl @Inject constructor( } } + override suspend fun getPopularUser(userType: String, category: String): Result> = + runCatching { + userService.getPopularUser(userType, category).mapper { + it.data?.map{ userDto -> userDto.toUser() } ?: throw ApiException.Unknown + } + } + override suspend fun fetchUserDetailInfo(id: String): Result = runCatching { // userService.postProfileDetailInfo(id).mapper { profileDetailResponse -> // ProfileDetailMapper.asProfileDetailModel(profileDetailResponse = profileDetailResponse) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt index 5d851cb6..647ecbd4 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt @@ -3,8 +3,9 @@ package com.eighteen.eighteenandroid.domain.di import com.eighteen.eighteenandroid.domain.repository.MyPageRepository import com.eighteen.eighteenandroid.domain.repository.UserRepository import com.eighteen.eighteenandroid.domain.usecase.CheckIdDuplicationUseCase -import com.eighteen.eighteenandroid.domain.usecase.GetUserUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetAnotherUserUseCase import com.eighteen.eighteenandroid.domain.usecase.GetMyProfileUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetPopularUserUseCase import com.eighteen.eighteenandroid.domain.usecase.GetUserDetailInfoUseCase import com.eighteen.eighteenandroid.domain.usecase.LoginUseCase import com.eighteen.eighteenandroid.domain.usecase.SignUpUseCase @@ -44,7 +45,11 @@ object UserUseCaseModule { @Provides @Singleton - fun provideGetUserUseCase(repository: UserRepository) = GetUserUseCase(repository = repository) + fun provideGetAnotherUserUseCase(repository: UserRepository) = GetAnotherUserUseCase(repository = repository) + + @Provides + @Singleton + fun provideGetPopularUserUseCase(repository: UserRepository) = GetPopularUserUseCase(repository = repository) @Provides @Singleton diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt index 7ba87b1f..1bc4afac 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/repository/UserRepository.kt @@ -3,6 +3,7 @@ package com.eighteen.eighteenandroid.domain.repository import com.eighteen.eighteenandroid.domain.model.AuthToken import com.eighteen.eighteenandroid.domain.model.Profile import com.eighteen.eighteenandroid.domain.model.SignUpInfo +import com.eighteen.eighteenandroid.domain.model.User import com.eighteen.eighteenandroid.domain.model.UserUseCaseModel import kotlinx.coroutines.flow.Flow @@ -13,7 +14,8 @@ interface UserRepository { fun getTokenFlow(): Flow suspend fun saveToken(authToken: AuthToken) suspend fun login(phoneNumber: String): Result - suspend fun fetchCategoryUser(userType: String, category: String, page: Int): Result + suspend fun getAnotherUser(userType: String, category: String, page: Int): Result + suspend fun getPopularUser(userType: String, category: String): Result> suspend fun postLikeUser(likedId: Int): Result suspend fun postLikeCancelUser(likedId: Int): Result } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetAnotherUserUseCase.kt similarity index 50% rename from app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt rename to app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetAnotherUserUseCase.kt index e920cecd..d7a0acd5 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetUserUseCase.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetAnotherUserUseCase.kt @@ -1,9 +1,8 @@ package com.eighteen.eighteenandroid.domain.usecase -import com.eighteen.eighteenandroid.common.enums.Tag import com.eighteen.eighteenandroid.domain.repository.UserRepository import javax.inject.Inject -class GetUserUseCase @Inject constructor(private val repository: UserRepository) { - suspend operator fun invoke(category: String, userType: String, page: Int) = repository.fetchCategoryUser(userType, category, page) +class GetAnotherUserUseCase @Inject constructor(private val repository: UserRepository) { + suspend operator fun invoke(category: String, userType: String, page: Int) = repository.getAnotherUser(userType, category, page) } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetPopularUserUseCase.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetPopularUserUseCase.kt new file mode 100644 index 00000000..4e661174 --- /dev/null +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/usecase/GetPopularUserUseCase.kt @@ -0,0 +1,8 @@ +package com.eighteen.eighteenandroid.domain.usecase + +import com.eighteen.eighteenandroid.domain.repository.UserRepository +import javax.inject.Inject + +class GetPopularUserUseCase @Inject constructor(private val repository: UserRepository) { + suspend operator fun invoke(category: String, userType: String) = repository.getPopularUser(userType, category) +} \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/selecttag/SignUpSelectTagViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/selecttag/SignUpSelectTagViewModel.kt index 3abf2d68..13de2367 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/selecttag/SignUpSelectTagViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/auth/signup/selecttag/SignUpSelectTagViewModel.kt @@ -22,6 +22,6 @@ class SignUpSelectTagViewModel : ViewModel() { } companion object { - private val tags = listOf(Tag.BEAUTY, Tag.EXERCISE, Tag.STUDY, Tag.ART, Tag.GAME, Tag.ETC) + private val tags = listOf(Tag.BEAUTY, Tag.SPORT, Tag.STUDY, Tag.ART, Tag.GAME, Tag.ETC) } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index e8333776..3ea762a8 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -379,15 +379,14 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl } is ModelState.Success -> { isLoading = false - mainAdapter.removeLoadingView() + if(isRequestNextPage.not()) { + moveToTop() + } + mainAdapter.removeLoadingView() it.data?.let { mainItems -> mainAdapter.updateView(mainItems) } - - if(isRequestNextPage.not()) { - moveToTop() - } } else ->{ // Error @@ -449,7 +448,13 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl category = tag } chip.setOnClickListener { _ -> + if(isLoading) { + // TODO. 토스트 ? + return@setOnClickListener + } + isRequestNextPage = false + mainAdapter.removeAllViews() selectedChip?.setTagStyle(isBlackBackground = false) chip.setTagStyle(isBlackBackground = true) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 6da49f29..871dad3e 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -9,11 +9,13 @@ import com.eighteen.eighteenandroid.common.ResourceProvider import com.eighteen.eighteenandroid.common.USER_TYPE_SIGNIN import com.eighteen.eighteenandroid.common.USER_TYPE_GUEST import com.eighteen.eighteenandroid.common.enums.Tag +import com.eighteen.eighteenandroid.data.mapper.ApiException import com.eighteen.eighteenandroid.domain.model.AboutTeen import com.eighteen.eighteenandroid.domain.model.MainItem import com.eighteen.eighteenandroid.domain.model.Tournament -import com.eighteen.eighteenandroid.domain.usecase.GetUserUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetAnotherUserUseCase import com.eighteen.eighteenandroid.domain.usecase.GetAuthTokenFlowUseCase +import com.eighteen.eighteenandroid.domain.usecase.GetPopularUserUseCase import com.eighteen.eighteenandroid.domain.usecase.UserLikeUseCase import com.eighteen.eighteenandroid.presentation.common.ModelState import dagger.hilt.android.lifecycle.HiltViewModel @@ -29,7 +31,8 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( private val resourceProvider: ResourceProvider, - private val getUserUseCase: GetUserUseCase, + private val getAnotherUserUseCase: GetAnotherUserUseCase, + private val getPopularUserUseCase: GetPopularUserUseCase, private val getAuthTokenFlowUseCase: GetAuthTokenFlowUseCase, private val userLikeUseCase: UserLikeUseCase ) : ViewModel() { @@ -65,43 +68,58 @@ class MainViewModel @Inject constructor( val items = mutableListOf() _mainItemStateFlow.value = ModelState.Loading() - // TODO. val popularTeenInitialState (인기 Teen) - - items.addAll( - listOf( - MainItem.HeaderView(resourceProvider.getString(R.string.main_today_teen)), - MainItem.UserListView( - emptyList() - ), // TODO. 인기 Teen - MainItem.DividerView, - MainItem.HeaderView(resourceProvider.getString(R.string.main_about_teen)), - MainItem.AboutTeenListView( - listOf( - AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), - AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), - AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), - AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") - ) - ), // About Teen List - MainItem.DividerView, - MainItem.HeaderWithMoreView(resourceProvider.getString(R.string.main_tournament_in_progress)), - MainItem.TournamentListView( - listOf( - Tournament.Exercise, - Tournament.Study - ) - ), // TODO. Tournament List - MainItem.DividerView, - MainItem.HeaderView(resourceProvider.getString(R.string.main_another_teen)) - ) - ) - // AuthToken 여부 확인 후 Fetch val authToken = getAuthTokenFlowUseCase.invoke().firstOrNull() + + // 인기 Teen + val popularTeenInitialState = if(authToken != null) { + getPopularUser(category, USER_TYPE_SIGNIN) + } else { + getPopularUser(category, USER_TYPE_GUEST) + } + + popularTeenInitialState.onSuccess { + with(items) { + add(MainItem.HeaderView(resourceProvider.getString(R.string.main_today_teen))) + add(MainItem.UserListView(it)) + add(MainItem.DividerView) + } + }.onFailure { + _mainItemStateFlow.value = ModelState.Error(throwable = it) + } + + with(items) { + add(MainItem.HeaderView(resourceProvider.getString(R.string.main_about_teen))) + add(MainItem.AboutTeenListView( + listOf( + AboutTeen("Teen", "친구들의 프로필을 투표해보세요!"), + AboutTeen("토너먼트", "투표 결과를 한 눈에 볼 수 있어요!"), + AboutTeen("채팅", "채팅을 통해 친구들과 소통해보세요!"), + AboutTeen("나만의 Teen", "나만의 프로필을 등록해보세요!") + ) + )) + add(MainItem.DividerView) + } + + // TODO. 토너먼트 목록 API 연결 + // val tournamentInitialState + + with(items) { + add(MainItem.HeaderWithMoreView(resourceProvider.getString(R.string.main_tournament_in_progress))) + add(MainItem.TournamentListView( + listOf( + Tournament.Exercise, + Tournament.Study + ) + )) + add(MainItem.DividerView) + } + + // 또 다른 Teen val anotherTeenInitialState = if (authToken != null) { - fetchUser(category, USER_TYPE_SIGNIN, 0) + getAnotherUser(category, USER_TYPE_SIGNIN, 0) } else { - fetchUser(category, USER_TYPE_GUEST, 0) + getAnotherUser(category, USER_TYPE_GUEST, 0) } anotherTeenInitialState.onSuccess { @@ -116,26 +134,25 @@ class MainViewModel @Inject constructor( _pageNumList.postValue(pages) } - it.users.forEach { user -> - items.add( - MainItem.UserView(user) - ) - } + with(items) { + add(MainItem.HeaderView(resourceProvider.getString(R.string.main_another_teen))) - _mainItemStateFlow.value = ModelState.Success(items) + it.users.forEach { user -> + add(MainItem.UserView(user)) + } + } }.onFailure { e -> _mainItemStateFlow.value = ModelState.Error(throwable = e) } - -// if( userDataState is ModelState.Success && aboutTeenDataState is ModelState.Success ) { -// // updateMain -// } else { -// // 에러화면 -// } + // 모두 가져오는 데에 성공하면 + if(anotherTeenInitialState.isSuccess && popularTeenInitialState.isSuccess) { + _mainItemStateFlow.value = ModelState.Success(items) + } else { + _mainItemStateFlow.value = ModelState.Error(throwable = ApiException.Unknown) + } } - /** 또 다른 Teen 무한 스크롤, 다음 페이지 유저 정보 가져오기 */ fun requestNextPage(category: Tag, page: Int) { viewModelScope.launch { @@ -148,9 +165,9 @@ class MainViewModel @Inject constructor( // AuthToken 여부 확인 후 Fetch val authToken = getAuthTokenFlowUseCase.invoke().firstOrNull() val anotherTeenInitialState = if (authToken != null) { - fetchUser(category, USER_TYPE_SIGNIN, page) + getAnotherUser(category, USER_TYPE_SIGNIN, page) } else { - fetchUser(category, USER_TYPE_GUEST, page) + getAnotherUser(category, USER_TYPE_GUEST, page) } anotherTeenInitialState @@ -177,9 +194,14 @@ class MainViewModel @Inject constructor( } } - private suspend fun fetchUser(category: Tag, userType: String, page: Int) = + private suspend fun getAnotherUser(category: Tag, userType: String, page: Int) = + viewModelScope.async { + getAnotherUserUseCase.invoke(category.name, userType, page) + }.await() + + private suspend fun getPopularUser(category: Tag, userType: String) = viewModelScope.async { - getUserUseCase.invoke(category.name, userType, page) + getPopularUserUseCase.invoke(category.name, userType) }.await() fun requestUserLike(isLike: Boolean, likedId: Int) { diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index ff2b78f3..7d57a9ac 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -360,6 +360,10 @@ class MainAdapter( } } + fun removeAllViews() { + submitList(emptyList()) + } + fun removeLoadingView() { val currentList = currentList.toMutableList() if(currentList.lastIndex > 0) currentList.removeAt(currentList.lastIndex) From 2a82ed36250b921a1ab0ec24b8f57d24b29b713d Mon Sep 17 00:00:00 2001 From: yys7517 Date: Mon, 3 Feb 2025 17:42:41 +0900 Subject: [PATCH 13/15] =?UTF-8?q?Conflict=20=ED=95=B4=EA=B2=B0=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt index 3d94d8ea..0e501e71 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/domain/di/UserUseCaseModule.kt @@ -21,10 +21,6 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object UserUseCaseModule { - @Provides - @Singleton - fun provideUserUseCase(repository: UserRepository): UserUseCase = - UserUseCase(repository = repository) @Provides @Singleton From c46c3f7455b63aa59381676cb3095a9a9d8d46f2 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 6 Feb 2025 12:20:33 +0900 Subject: [PATCH 14/15] =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eighteenandroid/presentation/home/MainFragment.kt | 3 +-- .../presentation/home/adapter/MainAdapter.kt | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 3ea762a8..14dc942f 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -283,8 +283,7 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl * 이전에 보던 인기 Teen 유저로 이동 */ override fun scrollToPreviousUser() { - val popularUserListViewHolder = - binding.rvMain.findViewHolderOrNull() + val popularUserListViewHolder = binding.rvMain.findViewHolderOrNull() val rvPopularUserList = popularUserListViewHolder?.binding?.rvMainTeenPopularList val layoutManager = rvPopularUserList?.layoutManager as? LinearLayoutManager diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index 7d57a9ac..1922dca4 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -253,7 +253,7 @@ class MainAdapter( } popularUserAdapter.submitList(userListView?.userList) { - rvMainTeenPopularList.doOnLayout { + rvMainTeenPopularList.post { listener.scrollToPreviousUser() } } @@ -348,7 +348,9 @@ class MainAdapter( } fun updateView(list: List) { - submitList(list) + submitList(list) { + listener.scrollToPreviousUser() + } } fun addLoadingView(scrollToPosition: (Int) -> Unit) { From 8f6849fd21663829a54ff5ac087dccc1511c9a59 Mon Sep 17 00:00:00 2001 From: yys7517 Date: Thu, 6 Feb 2025 15:25:42 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=8B=9C,=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EB=94=A9=20=EC=A0=81=EC=9A=A9=20#78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eighteenandroid/presentation/home/MainFragment.kt | 5 +---- .../eighteenandroid/presentation/home/MainViewModel.kt | 1 + .../eighteenandroid/presentation/home/adapter/MainAdapter.kt | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt index 14dc942f..be1c5f16 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainFragment.kt @@ -453,7 +453,6 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl } isRequestNextPage = false - mainAdapter.removeAllViews() selectedChip?.setTagStyle(isBlackBackground = false) chip.setTagStyle(isBlackBackground = true) @@ -466,12 +465,10 @@ class MainFragment : BaseFragment(FragmentMainBinding::infl chipGroup.addView(chip) } } - - // 전체 유저 가져오기 - // 페이지 0부터 } private fun getUserData(tag: Tag) { + mainAdapter.removeAllViews() viewModel.initMain(tag) } } \ No newline at end of file diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt index 871dad3e..a702a3e2 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/MainViewModel.kt @@ -68,6 +68,7 @@ class MainViewModel @Inject constructor( val items = mutableListOf() _mainItemStateFlow.value = ModelState.Loading() + delay(1500) // AuthToken 여부 확인 후 Fetch val authToken = getAuthTokenFlowUseCase.invoke().firstOrNull() diff --git a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt index 1922dca4..b6c099b0 100644 --- a/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt +++ b/app/src/main/java/com/eighteen/eighteenandroid/presentation/home/adapter/MainAdapter.kt @@ -363,7 +363,7 @@ class MainAdapter( } fun removeAllViews() { - submitList(emptyList()) + submitList(null) } fun removeLoadingView() {