Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -34,6 +34,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import com.example.hangsha_android.BuildConfig
import com.example.hangsha_android.ui.view.bookmarks.BookmarksScreen
import com.example.hangsha_android.ui.view.bookmarks.BookmarksViewModel
import com.example.hangsha_android.ui.view.bookmarks.GuestBookmarksViewModel
import com.example.hangsha_android.ui.view.calendar.CalendarFilterState
import com.example.hangsha_android.ui.view.login.LoginScreen
import com.example.hangsha_android.ui.view.login.LoginViewModel
Expand Down Expand Up @@ -536,10 +537,35 @@ fun NavGraphBuilder.mainGraph(navController: NavHostController) {
if (isLoggedIn) {
SimplePageText("bookmark events")
} else {
LoginRequiredScreen(
title = "북마크 목록은 로그인 후 이용할 수 있습니다.",
message = "게스트로 추가한 북마크는 캘린더 안에서 이 기기에만 저장됩니다.",
onLoginClick = { navController.navigateToLoginFromMain() }
val guestBookmarksViewModel: GuestBookmarksViewModel = hiltViewModel()
val guestBookmarksUiState by guestBookmarksViewModel.uiState.collectAsState()

BookmarksScreen(
uiState = guestBookmarksUiState,
onNavigateBack = {
navController.navigate(BottomTab.Calendar.route) {
popUpTo(HangshaDestinations.Main.route) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
onEventClick = { eventId ->
navController.navigate(HangshaDestinations.EventDetail.createRoute(eventId))
},
onBookmarkClick = { eventId ->
guestBookmarksViewModel.removeBookmark(eventId)
},
onRetryClick = { guestBookmarksViewModel.retry() },
onLoadNextPage = { guestBookmarksViewModel.loadNextPage() },
onScrollPositionChanged = { index, offset, itemId ->
guestBookmarksViewModel.saveScrollPosition(
firstVisibleItemIndex = index,
firstVisibleItemOffset = offset,
firstVisibleItemId = itemId
)
}
)
}
}
Expand All @@ -548,11 +574,27 @@ fun NavGraphBuilder.mainGraph(navController: NavHostController) {
val isLoggedIn by authStateViewModel.isLoggedIn.collectAsState()

if (!isLoggedIn) {
LoginRequiredScreen(
title = "내 북마크는 로그인 후 이용할 수 있습니다.",
message = "계정에 저장된 북마크 목록을 보려면 로그인해 주세요.",
onLoginClick = { navController.navigateToLoginFromMain() },
onNavigateBack = { navController.popBackStack() }
val guestBookmarksViewModel: GuestBookmarksViewModel = hiltViewModel()
val guestBookmarksUiState by guestBookmarksViewModel.uiState.collectAsState()

BookmarksScreen(
uiState = guestBookmarksUiState,
onNavigateBack = { navController.popBackStack() },
onEventClick = { eventId ->
navController.navigate(HangshaDestinations.EventDetail.createRoute(eventId))
},
onBookmarkClick = { eventId ->
guestBookmarksViewModel.removeBookmark(eventId)
},
onRetryClick = { guestBookmarksViewModel.retry() },
onLoadNextPage = { guestBookmarksViewModel.loadNextPage() },
onScrollPositionChanged = { index, offset, itemId ->
guestBookmarksViewModel.saveScrollPosition(
firstVisibleItemIndex = index,
firstVisibleItemOffset = offset,
firstVisibleItemId = itemId
)
}
)
} else {
val bookmarksViewModel: BookmarksViewModel = hiltViewModel()
Expand Down Expand Up @@ -611,57 +653,45 @@ fun NavGraphBuilder.mainGraph(navController: NavHostController) {
}
}
composable(HangshaDestinations.MyMemos.route) {
val authStateViewModel: AuthStateViewModel = hiltViewModel()
val isLoggedIn by authStateViewModel.isLoggedIn.collectAsState()

if (!isLoggedIn) {
LoginRequiredScreen(
title = "내 메모는 로그인 후 이용할 수 있습니다.",
message = "게스트 메모는 행사 상세 화면에서 이 기기에만 보관됩니다.",
onLoginClick = { navController.navigateToLoginFromMain() },
onNavigateBack = { navController.popBackStack() }
)
} else {
val myMemosViewModel: MyMemosViewModel = hiltViewModel()
val myMemosUiState by myMemosViewModel.uiState.collectAsState()
val context = LocalContext.current
val myMemosLifecycleOwner = LocalLifecycleOwner.current
val myMemosViewModel: MyMemosViewModel = hiltViewModel()
val myMemosUiState by myMemosViewModel.uiState.collectAsState()
val context = LocalContext.current
val myMemosLifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(myMemosUiState.toastMessage) {
val message = myMemosUiState.toastMessage ?: return@LaunchedEffect
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
myMemosViewModel.onToastMessageConsumed()
}
LaunchedEffect(myMemosUiState.toastMessage) {
val message = myMemosUiState.toastMessage ?: return@LaunchedEffect
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
myMemosViewModel.onToastMessageConsumed()
}

DisposableEffect(myMemosLifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
myMemosViewModel.loadMemos()
}
}
myMemosLifecycleOwner.lifecycle.addObserver(observer)
onDispose {
myMemosLifecycleOwner.lifecycle.removeObserver(observer)
DisposableEffect(myMemosLifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
myMemosViewModel.loadMemos()
}
}

MyMemosScreen(
uiState = myMemosUiState,
onNavigateBack = { navController.popBackStack() },
onMemoClick = { eventId ->
navController.navigate(HangshaDestinations.EventDetail.createRoute(eventId))
},
onDeleteMemoClick = { memoId -> myMemosViewModel.deleteMemo(memoId) },
onStartEditMemo = { memo -> myMemosViewModel.startEditMemo(memo) },
onEditContentChanged = { value -> myMemosViewModel.onEditContentChanged(value) },
onStartAddingTag = { myMemosViewModel.startAddingTag() },
onEditTagInputChanged = { value -> myMemosViewModel.onEditTagInputChanged(value) },
onAddEditTag = { myMemosViewModel.addEditTag() },
onRemoveEditTag = { tagName -> myMemosViewModel.removeEditTag(tagName) },
onSaveEditedMemo = { myMemosViewModel.saveEditedMemo() },
onRetryClick = { myMemosViewModel.loadMemos() }
)
myMemosLifecycleOwner.lifecycle.addObserver(observer)
onDispose {
myMemosLifecycleOwner.lifecycle.removeObserver(observer)
}
}

MyMemosScreen(
uiState = myMemosUiState,
onNavigateBack = { navController.popBackStack() },
onMemoClick = { eventId ->
navController.navigate(HangshaDestinations.EventDetail.createRoute(eventId))
},
onDeleteMemoClick = { memoId -> myMemosViewModel.deleteMemo(memoId) },
onStartEditMemo = { memo -> myMemosViewModel.startEditMemo(memo) },
onEditContentChanged = { value -> myMemosViewModel.onEditContentChanged(value) },
onStartAddingTag = { myMemosViewModel.startAddingTag() },
onEditTagInputChanged = { value -> myMemosViewModel.onEditTagInputChanged(value) },
onAddEditTag = { myMemosViewModel.addEditTag() },
onRemoveEditTag = { tagName -> myMemosViewModel.removeEditTag(tagName) },
onSaveEditedMemo = { myMemosViewModel.saveEditedMemo() },
onRetryClick = { myMemosViewModel.loadMemos() }
)
}
composable(
route = HangshaDestinations.InterestPriority.route,
Expand Down Expand Up @@ -730,20 +760,10 @@ fun NavGraphBuilder.mainGraph(navController: NavHostController) {
if (!isLoggedIn) {
val guestMyPageViewModel: GuestMyPageViewModel = hiltViewModel()
val guestMyPageUiState by guestMyPageViewModel.uiState.collectAsState()
val context = LocalContext.current

LaunchedEffect(guestMyPageUiState.bugReportToastMessage) {
val message = guestMyPageUiState.bugReportToastMessage ?: return@LaunchedEffect
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
guestMyPageViewModel.onBugReportToastConsumed()
}

GuestMyPageScreen(
uiState = guestMyPageUiState,
onLoginClick = { navController.navigateToLoginFromMain() },
onInterestPriorityClick = {
navController.navigate(HangshaDestinations.InterestPriority.createRoute())
},
onTimetableClick = {
navController.navigate(BottomTab.Timetable.route) {
popUpTo(HangshaDestinations.Main.route) {
Expand All @@ -764,15 +784,6 @@ fun NavGraphBuilder.mainGraph(navController: NavHostController) {
},
onMemoEventClick = { eventId ->
navController.navigate(HangshaDestinations.EventDetail.createRoute(eventId))
},
onBugReportTitleChanged = { value ->
guestMyPageViewModel.onBugReportTitleChanged(value)
},
onBugReportContentChanged = { value ->
guestMyPageViewModel.onBugReportContentChanged(value)
},
onSubmitBugReportClick = {
guestMyPageViewModel.submitBugReport()
}
)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.example.hangsha_android.ui.view.bookmarks

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.hangsha_android.data.local.BookmarksLocalDataSource
import com.example.hangsha_android.data.local.StoredGuestBookmarkSnapshot
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

@HiltViewModel
class GuestBookmarksViewModel @Inject constructor(
private val bookmarksLocalDataSource: BookmarksLocalDataSource
) : ViewModel() {
private val _uiState = MutableStateFlow(BookmarksUiState())
val uiState: StateFlow<BookmarksUiState> = _uiState.asStateFlow()

init {
viewModelScope.launch {
combine(
bookmarksLocalDataSource.bookmarkedEventIds(isLoggedIn = false),
bookmarksLocalDataSource.guestBookmarkSnapshots
) { bookmarkedEventIds, bookmarkSnapshots ->
val current = _uiState.value
current.copy(
items = bookmarkSnapshots.toBookmarkedEventItems(bookmarkedEventIds),
isInitialLoading = false,
isLoadingNextPage = false,
hasMore = false,
nextPage = 1,
errorMessage = null
)
}.collect { nextState ->
_uiState.update { nextState }
}
}
}

fun removeBookmark(eventId: Long) {
viewModelScope.launch {
bookmarksLocalDataSource.setBookmarked(
eventId = eventId,
isBookmarked = false,
isLoggedIn = false
)
}
}

fun retry() = Unit

fun loadNextPage() = Unit

fun saveScrollPosition(
firstVisibleItemIndex: Int,
firstVisibleItemOffset: Int,
firstVisibleItemId: Long?
) {
_uiState.update {
it.copy(
savedScrollIndex = firstVisibleItemIndex,
savedScrollOffset = firstVisibleItemOffset,
savedAnchorItemId = firstVisibleItemId
)
}
}
}

private fun List<StoredGuestBookmarkSnapshot>.toBookmarkedEventItems(
bookmarkedEventIds: Set<Long>
): List<BookmarkedEventItem> {
val snapshotsByEventId = associateBy { it.eventId }
return bookmarkedEventIds.map { eventId ->
snapshotsByEventId[eventId]?.toBookmarkedEventItem()
?: BookmarkedEventItem(
id = eventId,
title = "Event #$eventId",
imageUrl = null,
eventTypeId = 0L,
statusId = 0L,
dDayLabel = "-",
applyPeriodDisplay = "-",
organization = null,
isBookmarked = true
)
}.sortedByDescending { item -> snapshotsByEventId[item.id]?.updatedAt.orEmpty() }
}

private fun StoredGuestBookmarkSnapshot.toBookmarkedEventItem(): BookmarkedEventItem {
return BookmarkedEventItem(
id = eventId,
title = title,
imageUrl = imageUrl,
eventTypeId = eventTypeId,
statusId = 0L,
dDayLabel = dDayLabel.toDdayLabel(),
applyPeriodDisplay = applyPeriodDisplay ?: "-",
organization = organization,
isBookmarked = true
)
}

private fun String?.toDdayLabel(): String {
val normalized = this?.trim().orEmpty()
.removePrefix("Apply")
.removePrefix("지원")
.trim()
if (normalized.isBlank()) {
return "-"
}
return when {
normalized.equals("D-DAY", ignoreCase = true) -> "D-day"
normalized.startsWith("D") -> normalized
else -> normalized
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,61 +30,23 @@ class GuestMyPageViewModel @Inject constructor(
bookmarksLocalDataSource.guestBookmarkSnapshots,
guestMemosLocalDataSource.memos
) { bookmarkedEventIds, bookmarkSnapshots, memos ->
val current = _uiState.value
GuestMyPageUiState(
bookmarkItems = bookmarkSnapshots.toGuestBookmarkPreviewItems(bookmarkedEventIds),
bookmarkCount = bookmarkedEventIds.size,
memoItems = memos.map { it.toGuestMemoPreviewItem() },
bugReportTitle = current.bugReportTitle,
bugReportContent = current.bugReportContent,
isSubmittingBugReport = current.isSubmittingBugReport,
bugReportToastMessage = current.bugReportToastMessage
memoItems = memos.map { it.toGuestMemoPreviewItem() }
)
}.collect { nextState ->
_uiState.update { nextState }
}
}
}

fun onBugReportTitleChanged(value: String) {
_uiState.update {
it.copy(
bugReportTitle = value,
bugReportToastMessage = null
)
}
}

fun onBugReportContentChanged(value: String) {
_uiState.update {
it.copy(
bugReportContent = value,
bugReportToastMessage = null
)
}
}

fun submitBugReport() {
_uiState.update {
it.copy(bugReportToastMessage = "Please log in to submit a bug report.")
}
}

fun onBugReportToastConsumed() {
_uiState.update {
it.copy(bugReportToastMessage = null)
}
}
}

data class GuestMyPageUiState(
val bookmarkItems: List<GuestBookmarkPreviewItem> = emptyList(),
val bookmarkCount: Int = 0,
val memoItems: List<GuestMemoPreviewItem> = emptyList(),
val bugReportTitle: String = "",
val bugReportContent: String = "",
val isSubmittingBugReport: Boolean = false,
val bugReportToastMessage: String? = null
val memoItems: List<GuestMemoPreviewItem> = emptyList()
) {
val memoCount: Int
get() = memoItems.size
Expand Down
Loading
Loading