Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AN/USER] feat: 북마크 추가 버튼 기능 구현 및 API 연동(#922) #923

Merged
merged 36 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
977a991
feat(SignInActivity): 로그인 화면 구현
SeongHoonC Apr 15, 2024
ddfcb9e
feat(SplashEvent): 로그인 했거나 로그인을 거부했다면 앱 시작 시 로그인 화면이 다시 뜨지 않는다.
SeongHoonC Apr 16, 2024
b681ffc
feat(SignInViewModel): 로그인 이벤트 관리
SeongHoonC Apr 16, 2024
e86c611
feat(FakeAuthRepository): 가짜 Auth 저장소 정의
SeongHoonC Apr 16, 2024
78f0d49
feat: auth response 객체 정의
SeongHoonC Apr 24, 2024
b922c08
feat: 토큰 저장을 위한 엔티티 정의
SeongHoonC Apr 24, 2024
cb865fc
feat: 토큰 저장소 정의
SeongHoonC Apr 24, 2024
cb971df
feat: 토큰 도메인 객체 정의
SeongHoonC Apr 24, 2024
e9663a7
feat: UserRetrofitService 임시 정의
SeongHoonC Apr 24, 2024
bde05c5
feat: AuthRepository rename to UserRepository & 재정의
SeongHoonC Apr 24, 2024
653c748
Merge branch 'dev' into feat/#861
SeongHoonC Apr 27, 2024
fdc27b3
feat(AuthRetrofitService): 유저 인증 API 요청 서비스 정의
SeongHoonC Apr 28, 2024
5634a61
feat(DefaultUserRepository): 유저 저장소는 회원가입, 리프레시, 로그아웃, 회원탈퇴 기능을 수행한다.
SeongHoonC Apr 28, 2024
d92622c
feat(UnauthorizedException): 인증 정보 오류에 대한 Result 확장함수 정의
SeongHoonC Apr 28, 2024
717d9ae
feat(DataSourceModule): 토큰 관리 데이터 저장소 정의
SeongHoonC Apr 28, 2024
17d5a1f
feat: Kakao 인증 방식을 code 에서 IdToken 으로 변경한다
SeongHoonC Apr 29, 2024
0b53474
feat: AuthRetrofit 은 AuthInterceptor 를 사용해 토큰과 함께 요청한다.
SeongHoonC May 1, 2024
1af4539
feat: Token util 제거
SeongHoonC May 1, 2024
9ca8dfe
feat: 카카오 로그아웃 회원탈퇴 로직 이동
SeongHoonC May 1, 2024
a26213b
chore: DataSource 패키지 정리
SeongHoonC May 1, 2024
15ed793
feat: KakaoAuth 를 common 으로 이동
SeongHoonC May 1, 2024
a1f5870
feat: 로그인 화면 및 User 저장소에 kakao Auth 주입
SeongHoonC May 1, 2024
3fc1dd0
feat: shardPref 에 object 넣기 Util 화
SeongHoonC May 1, 2024
3e6b178
feat: UserInfo 를 저장할 수 있다.
SeongHoonC May 1, 2024
e9e3c3b
feat: 유저 정보를 가져올 수 있다.
SeongHoonC May 1, 2024
2a6afbd
fix(AuthInterceptor): API 요청 token 인터셉트 로직 수정
SeongHoonC May 1, 2024
f8a6538
feat(bookmark): 북마크 연동
re4rk May 1, 2024
6797f0c
feat(bookmark): 북마크 아티스트 추가
re4rk May 1, 2024
ace6e95
feat(Bookmark): 학교 북마크 추가기능 구현
re4rk May 1, 2024
f2b886b
feat(Bookmark): 축제 북마크 추가기능 구현
re4rk May 1, 2024
d7cf0ad
feat(Bookmark): 축제 북마크 에러 화면 분기처리 수정
re4rk May 1, 2024
f7e3b71
refactor(Bookmark): 버그 수정
re4rk May 1, 2024
c50ff8d
refactor(Bookmark): 최대 개수 초과시 안내 매세지 출력
re4rk May 1, 2024
1f67765
Merge branch 'dev' into feat/#922
re4rk May 1, 2024
5da4597
refactor(Bookmark): ktlint
re4rk May 1, 2024
d6b0563
refactor(Bookmark): ktlint
re4rk May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.festago.festago.data.di.singletonscope

import com.festago.festago.data.repository.DefaultArtistRepository
import com.festago.festago.data.repository.DefaultBookmarkRepository
import com.festago.festago.data.repository.DefaultFestivalRepository
import com.festago.festago.data.repository.DefaultRecentSearchRepository
import com.festago.festago.data.repository.DefaultSchoolRepository
import com.festago.festago.data.repository.DefaultSearchRepository
import com.festago.festago.data.repository.DefaultUserRepository
import com.festago.festago.data.repository.FakeBookmarkRepository
import com.festago.festago.domain.repository.ArtistRepository
import com.festago.festago.domain.repository.BookmarkRepository
import com.festago.festago.domain.repository.FestivalRepository
Expand Down Expand Up @@ -45,7 +45,7 @@ interface RepositoryModule {

@Binds
@Singleton
fun bindsBookmarkRepository(bookmarkRepository: FakeBookmarkRepository): BookmarkRepository
fun bindsBookmarkRepository(bookmarkRepository: DefaultBookmarkRepository): BookmarkRepository

@Binds
@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object ServiceModule {
@Provides
@Singleton
fun providesBookmarkRetrofitService(
@NormalRetrofitQualifier retrofit: Retrofit,
@AuthRetrofitQualifier retrofit: Retrofit,
): BookmarkRetrofitService {
return retrofit.create(BookmarkRetrofitService::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ class DefaultBookmarkRepository @Inject constructor(
}

override suspend fun deleteFestivalBookmark(festivalId: Long): Result<Unit> {
return runCatchingResponse {
val result = runCatchingResponse {
bookmarkRetrofitService.deleteBookmark(festivalId, BookmarkType.FESTIVAL)
}
result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
return result
}

override suspend fun addSchoolBookmark(schoolId: Long): Result<Unit> {
Expand All @@ -59,9 +61,11 @@ class DefaultBookmarkRepository @Inject constructor(
}

override suspend fun deleteSchoolBookmark(schoolId: Long): Result<Unit> {
return runCatchingResponse {
val result = runCatchingResponse {
bookmarkRetrofitService.deleteBookmark(schoolId, BookmarkType.SCHOOL)
}
result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
return result
}

override suspend fun addArtistBookmark(artistId: Long): Result<Unit> {
Expand All @@ -81,8 +85,10 @@ class DefaultBookmarkRepository @Inject constructor(
}

override suspend fun deleteArtistBookmark(artistId: Long): Result<Unit> {
return runCatchingResponse {
val result = runCatchingResponse {
bookmarkRetrofitService.deleteBookmark(artistId, BookmarkType.ARTIST)
}
result.onFailure { if (it.message?.contains("204") == true) return Result.success(Unit) }
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ sealed interface ArtistDetailEvent {
class ShowArtistDetail(val artistId: Long) : ArtistDetailEvent

class ShowFestivalDetail(val festivalId: Long) : ArtistDetailEvent

class FailedToFetchBookmarkList(val message: String) : ArtistDetailEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
Expand Down Expand Up @@ -53,9 +54,7 @@ class ArtistDetailFragment : Fragment() {
requireActivity().onBackPressedDispatcher.onBackPressed()
}

binding.cvBookmark.setOnClickListener {
binding.ivBookmark.isSelected = !binding.ivBookmark.isSelected
}
binding.cvBookmark.isSelected
}

private fun initObserve() {
Expand All @@ -81,6 +80,8 @@ class ArtistDetailFragment : Fragment() {
private fun handleSuccess(uiState: ArtistDetailUiState.Success) {
binding.successUiState = uiState

binding.ivBookmark.isSelected = uiState.bookMarked

val items: List<Any> = if (uiState.isLast) {
uiState.festivals
} else {
Expand Down Expand Up @@ -117,6 +118,11 @@ class ArtistDetailFragment : Fragment() {
),
)
}

is ArtistDetailEvent.FailedToFetchBookmarkList -> {
Toast.makeText(requireContext(), "최대 북마크 갯수를 초과했습니다", Toast.LENGTH_SHORT)
.show()
}
}

private fun startBrowser(url: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.domain.model.festival.FestivalsPage
import com.festago.festago.domain.repository.ArtistRepository
import com.festago.festago.domain.repository.BookmarkRepository
import com.festago.festago.presentation.ui.artistdetail.ArtistDetailEvent.FailedToFetchBookmarkList
import com.festago.festago.presentation.ui.artistdetail.uistate.ArtistDetailUiState
import com.festago.festago.presentation.ui.artistdetail.uistate.ArtistUiState
import com.festago.festago.presentation.ui.artistdetail.uistate.FestivalItemUiState
Expand All @@ -24,6 +26,7 @@ import javax.inject.Inject
@HiltViewModel
class ArtistDetailViewModel @Inject constructor(
private val artistRepository: ArtistRepository,
private val bookmarkRepository: BookmarkRepository,
private val analyticsHelper: AnalyticsHelper,
) : ViewModel() {
private val _event: MutableSharedFlow<ArtistDetailEvent> = MutableSharedFlow()
Expand All @@ -40,12 +43,17 @@ class ArtistDetailViewModel @Inject constructor(
runCatching {
val deferredArtistDetail = async { artistRepository.loadArtistDetail(id) }
val deferredFestivals = async { artistRepository.loadArtistFestivals(id, 10) }
val deferredBookmarks = async { bookmarkRepository.getArtistBookmarks() }
val artist = deferredArtistDetail.await().getOrThrow()
val festivalPage = deferredFestivals.await().getOrThrow()
val artistBookmarks = deferredBookmarks.await().getOrThrow()

_uiState.value = ArtistDetailUiState.Success(
deferredArtistDetail.await().getOrThrow(),
festivalPage.toUiState(),
festivalPage.isLastPage,
artist = artist,
bookMarked = artistBookmarks.firstOrNull { it.artist.id == artist.id.toLong() } != null,
festivals = festivalPage.toUiState(),
isLast = festivalPage.isLastPage,
onBookmarkClick = ::toggleArtistBookmark,
)

if (festivalPage.festivals.isEmpty()) {
Expand Down Expand Up @@ -85,6 +93,21 @@ class ArtistDetailViewModel @Inject constructor(
}
}

private fun toggleArtistBookmark(artistId: Int) {
viewModelScope.launch {
val uiState = uiState.value as? ArtistDetailUiState.Success ?: return@launch

if (uiState.bookMarked) {
bookmarkRepository.deleteArtistBookmark(artistId.toLong())
.onSuccess { _uiState.value = uiState.copy(bookMarked = false) }
.onFailure { _event.emit(FailedToFetchBookmarkList("최대 북마크 갯수를 초과했습니다")) }
} else {
bookmarkRepository.addArtistBookmark(artistId.toLong())
.onSuccess { _uiState.value = uiState.copy(bookMarked = true) }
.onFailure { _event.emit(FailedToFetchBookmarkList("최대 북마크 갯수를 초과했습니다")) }
}
}
}
private fun handleFailure(key: String, throwable: Throwable) {
_uiState.value = ArtistDetailUiState.Error {
_uiState.value = ArtistDetailUiState.Loading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ sealed interface ArtistDetailUiState {

data class Success(
val artist: ArtistDetail,
val bookMarked: Boolean,
val festivals: List<FestivalItemUiState>,
val isLast: Boolean,
val onBookmarkClick: (Int) -> Unit,
) : ArtistDetailUiState

class Error(val refresh: (id: Long) -> Unit) : ArtistDetailUiState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.festago.festago.presentation.ui.festivaldetail
sealed interface FestivalDetailEvent {
class ShowArtistDetail(val artistId: Long) : FestivalDetailEvent
class ShowSchoolDetail(val schoolId: Long) : FestivalDetailEvent
class FailedToFetchBookmarkList(val message: String) : FestivalDetailEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
Expand Down Expand Up @@ -56,9 +57,6 @@ class FestivalDetailFragment : Fragment() {
binding.ivBack.setOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
binding.cvBookmark.setOnClickListener {
binding.ivBookmark.isSelected = !binding.ivBookmark.isSelected
}
}

private fun initObserve() {
Expand All @@ -85,6 +83,7 @@ class FestivalDetailFragment : Fragment() {

private fun handleSuccess(uiState: FestivalDetailUiState.Success) {
binding.successUiState = uiState
binding.ivBookmark.isSelected = uiState.bookmarked
binding.tvFestivalDDay.setFestivalDDay(uiState.festival.startDate, uiState.festival.endDate)
binding.ivFestivalBackground.setColorFilter(Color.parseColor("#66000000"))
adapter.submitList(uiState.stages)
Expand Down Expand Up @@ -160,6 +159,10 @@ class FestivalDetailFragment : Fragment() {
),
)
}

is FestivalDetailEvent.FailedToFetchBookmarkList -> {
Toast.makeText(requireContext(), event.message, Toast.LENGTH_SHORT).show()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.domain.model.artist.Artist
import com.festago.festago.domain.model.festival.FestivalDetail
import com.festago.festago.domain.model.stage.Stage
import com.festago.festago.domain.repository.BookmarkRepository
import com.festago.festago.domain.repository.FestivalRepository
import com.festago.festago.presentation.ui.festivaldetail.FestivalDetailEvent.FailedToFetchBookmarkList
import com.festago.festago.presentation.ui.festivaldetail.uiState.ArtistItemUiState
import com.festago.festago.presentation.ui.festivaldetail.uiState.FestivalDetailUiState
import com.festago.festago.presentation.ui.festivaldetail.uiState.FestivalUiState
Expand All @@ -25,6 +27,7 @@ import javax.inject.Inject
@HiltViewModel
class FestivalDetailViewModel @Inject constructor(
private val festivalRepository: FestivalRepository,
private val bookmarkRepository: BookmarkRepository,
private val analyticsHelper: AnalyticsHelper,
) : ViewModel() {

Expand All @@ -38,8 +41,15 @@ class FestivalDetailViewModel @Inject constructor(
if (!refresh && _uiState.value is FestivalDetailUiState.Success) return

viewModelScope.launch {
festivalRepository.loadFestivalDetail(festivalId).onSuccess { festivalDetail ->
val deferredFestivalDetail = festivalRepository.loadFestivalDetail(festivalId)
val deferredBookmarks = bookmarkRepository.getFestivalBookmarkIds()

runCatching {
val festivalDetail = deferredFestivalDetail.getOrThrow()
val festivalBookmarkIds = deferredBookmarks.getOrThrow()

_uiState.value = festivalDetail.toSuccessUiState()
.copy(bookmarked = festivalBookmarkIds.contains(festivalId))
}.onFailure {
_uiState.value = FestivalDetailUiState.Error { festivalId ->
_uiState.value = FestivalDetailUiState.Loading
Expand All @@ -53,20 +63,37 @@ class FestivalDetailViewModel @Inject constructor(
}
}

private fun FestivalDetail.toSuccessUiState() =
FestivalDetailUiState.Success(
FestivalUiState(
id = id,
name = name,
startDate = startDate,
endDate = endDate,
posterImageUrl = posterImageUrl,
school = school,
onSchoolClick = ::showSchoolDetail,
socialMedias = socialMedias,
),
stages = stages.map { it.toUiState() },
)
private fun FestivalDetail.toSuccessUiState() = FestivalDetailUiState.Success(
festival = FestivalUiState(
id = id,
name = name,
startDate = startDate,
endDate = endDate,
posterImageUrl = posterImageUrl,
school = school,
onSchoolClick = ::showSchoolDetail,
socialMedias = socialMedias,
),
bookmarked = false,
stages = stages.map { it.toUiState() },
onBookmarkClick = { festivalId -> toggleFestivalBookmark(festivalId) },
)

private fun toggleFestivalBookmark(festivalId: Long) {
viewModelScope.launch {
val uiState = _uiState.value as? FestivalDetailUiState.Success ?: return@launch

if (uiState.bookmarked) {
bookmarkRepository.deleteFestivalBookmark(festivalId)
.onSuccess { _uiState.value = uiState.copy(bookmarked = false) }
.onFailure { _event.emit(FailedToFetchBookmarkList("최대 북마크 갯수를 초과했습니다")) }
} else {
bookmarkRepository.addFestivalBookmark(festivalId)
.onSuccess { _uiState.value = uiState.copy(bookmarked = true) }
.onFailure { _event.emit(FailedToFetchBookmarkList("최대 북마크 갯수를 초과했습니다")) }
}
}
}

private fun Stage.toUiState() = StageItemUiState(
id = id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ interface FestivalDetailUiState {

data class Success(
val festival: FestivalUiState,
val bookmarked: Boolean,
val stages: List<StageItemUiState>,
val onBookmarkClick: (Long) -> Unit,
) : FestivalDetailUiState

class Error(val refresh: (id: Long) -> Unit) : FestivalDetailUiState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,21 @@ class FestivalBookmarkViewModel @Inject constructor(
fun fetchBookmarkList() {
viewModelScope.launch {
_uiState.value = FestivalBookmarkUiState.Loading
bookmarkRepository.getFestivalBookmarkIds().onSuccess { bookmarkIds ->
bookmarkRepository.getFestivalBookmarks(bookmarkIds, FestivalBookmarkOrder.FESTIVAL)
.onSuccess { festivalBookmarks ->
_uiState.value =
FestivalBookmarkUiState.Success(festivalBookmarks.map { it.toUiState() })
}.onFailure {
_uiState.value = FestivalBookmarkUiState.Error
}
}.onFailure {
_uiState.value = FestivalBookmarkUiState.Error
val bookmarkIds = bookmarkRepository.getFestivalBookmarkIds()
.getOrElse { _uiState.value = FestivalBookmarkUiState.Error; return@launch }

if (bookmarkIds.isEmpty()) {
_uiState.value = FestivalBookmarkUiState.Success(emptyList())
return@launch
}

bookmarkRepository.getFestivalBookmarks(bookmarkIds, FestivalBookmarkOrder.FESTIVAL)
.onSuccess { festivalBookmarks ->
_uiState.value =
FestivalBookmarkUiState.Success(festivalBookmarks.map { it.toUiState() })
}.onFailure {
_uiState.value = FestivalBookmarkUiState.Error
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ sealed interface SchoolDetailEvent {
class ShowArtistDetail(val artistId: Long) : SchoolDetailEvent

class ShowFestivalDetail(val festivalId: Long) : SchoolDetailEvent

class FailedToFetchBookmarkList(val message: String) : SchoolDetailEvent
}
Loading
Loading