diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 520a514e..fe780ac9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,6 +14,7 @@ plugins { alias(libs.plugins.ksp) alias(libs.plugins.hilt) alias(libs.plugins.google.services) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.firebase.crashlytics) } @@ -123,6 +124,7 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + implementation(libs.kotlinx.immutable) // Hilt implementation(libs.hilt.android) @@ -157,6 +159,9 @@ dependencies { implementation(libs.androidx.paging.runtime) implementation(libs.androidx.paging.compose.android) + // Serialization + implementation(libs.kotlinx.serialization.json) + // Credentials implementation(libs.androidx.credentials) implementation(libs.androidx.credentials.play.services.auth) diff --git a/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt b/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt index f902e027..b49c6a3c 100644 --- a/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/MessageAlertDialog.kt @@ -31,9 +31,10 @@ import com.squirtles.musicroad.ui.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun MessageAlertDialog( - onDismissRequest: () -> Unit, title: String, body: String, + onDismissRequest: () -> Unit, + showBody: Boolean = true, buttons: @Composable RowScope.() -> Unit, ) { BasicAlertDialog( @@ -55,13 +56,15 @@ internal fun MessageAlertDialog( style = MaterialTheme.typography.bodyLarge ) - VerticalSpacer(8) + if (showBody) { + VerticalSpacer(8) - Text( - text = body, - color = Black, - style = MaterialTheme.typography.bodyLarge - ) + Text( + text = body, + color = Black, + style = MaterialTheme.typography.bodyLarge + ) + } VerticalSpacer(24) diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/PickListScreen.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt similarity index 86% rename from app/src/main/java/com/squirtles/musicroad/picklist/PickListScreen.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt index d0b5d272..a33e8cbc 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/PickListScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListScreenContents.kt @@ -20,7 +20,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -33,8 +32,6 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.material.CircularProgressIndicator import com.squirtles.domain.model.Order import com.squirtles.domain.model.Pick @@ -44,29 +41,42 @@ import com.squirtles.musicroad.common.Constants.DEFAULT_PADDING import com.squirtles.musicroad.common.CountText import com.squirtles.musicroad.common.DefaultTopAppBar import com.squirtles.musicroad.common.VerticalSpacer +import com.squirtles.musicroad.common.picklist.PickListType +import com.squirtles.musicroad.common.picklist.PickListUiState +import com.squirtles.musicroad.common.picklist.components.DeleteSelectedPickDialog +import com.squirtles.musicroad.common.picklist.components.EditModeAction +import com.squirtles.musicroad.common.picklist.components.EditModeBottomButton +import com.squirtles.musicroad.common.picklist.components.OrderBottomSheet +import com.squirtles.musicroad.common.picklist.components.PickItem import com.squirtles.musicroad.ui.theme.Primary import com.squirtles.musicroad.ui.theme.White @Composable -fun PickListScreen( +fun PickListScreenContents( userId: String, + showOrderBottomSheet: Boolean, + selectedPicksId: Set, pickListType: PickListType, + uiState: PickListUiState, + getUserId: () -> String, onBackClick: () -> Unit, onItemClick: (String) -> Unit, - pickListViewModel: PickListViewModel = hiltViewModel() + setListOrder: (Order) -> Unit, + setOrderBottomSheetVisibility: (Boolean) -> Unit, + selectAllPicks: () -> Unit, + deselectAllPicks: () -> Unit, + toggleSelectedPick: (String) -> Unit, + deleteSelectedPicks: (String) -> Unit ) { val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - val uiState by pickListViewModel.pickListUiState.collectAsStateWithLifecycle() - val selectedPicksId by pickListViewModel.selectedPicksId.collectAsStateWithLifecycle() var isEditMode by rememberSaveable { mutableStateOf(false) } - var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } var isDeletePickDialogVisible by rememberSaveable { mutableStateOf(false) } val deactivateEditMode = remember { { isEditMode = false - pickListViewModel.deselectAllPicks() + deselectAllPicks() } } val showDeletePickDialog = remember { @@ -75,13 +85,6 @@ fun PickListScreen( } } - LaunchedEffect(Unit) { - when (pickListType) { - PickListType.FAVORITE -> pickListViewModel.fetchFavoritePicks(userId) - PickListType.CREATED -> pickListViewModel.fetchMyPicks(userId) - } - } - Scaffold( topBar = { DefaultTopAppBar( @@ -93,14 +96,14 @@ fun PickListScreen( ), onBackClick = onBackClick, actions = { - if (pickListViewModel.getUserId() == userId) { + if(getUserId() == userId){ EditModeAction( isEditMode = isEditMode, enabled = uiState is PickListUiState.Success, isSelectedEmpty = selectedPicksId.isEmpty(), activateEditMode = { isEditMode = true }, - selectAllPicks = { pickListViewModel.selectAllPicks() }, - deselectAllPicks = { pickListViewModel.deselectAllPicks() }, + selectAllPicks = { selectAllPicks() }, + deselectAllPicks = { deselectAllPicks() }, ) } } @@ -125,8 +128,8 @@ fun PickListScreen( } is PickListUiState.Success -> { - val pickList = (uiState as PickListUiState.Success).pickList - val order = (uiState as PickListUiState.Success).order + val pickList = uiState.pickList + val order = uiState.order Column( modifier = Modifier.fillMaxSize() @@ -140,9 +143,11 @@ fun PickListScreen( pickList = pickList, selectedPicksId = selectedPicksId, order = order, - onOrderClick = { showOrderBottomSheet = true }, + onOrderClick = { + setOrderBottomSheetVisibility(true) + }, onItemClick = onItemClick, - onEditModeItemClick = { pickListViewModel.toggleSelectedPick(it) }, + onEditModeItemClick = toggleSelectedPick, ) if (isEditMode) { @@ -172,10 +177,8 @@ fun PickListScreen( OrderBottomSheet( isFavoritePicks = pickListType == PickListType.FAVORITE, currentOrder = (uiState as PickListUiState.Success).order, - onDismissRequest = { showOrderBottomSheet = false }, - onOrderClick = { order -> - pickListViewModel.setListOrder(pickListType, order) - }, + onDismissRequest = { setOrderBottomSheetVisibility(false) }, + onOrderClick = setListOrder, ) } @@ -187,7 +190,7 @@ fun PickListScreen( onDeletePickClick = { isEditMode = false isDeletePickDialogVisible = false - pickListViewModel.deleteSelectedPicks(pickListType) + deleteSelectedPicks(userId) }, ) } diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/PickListType.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt similarity index 51% rename from app/src/main/java/com/squirtles/musicroad/picklist/PickListType.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt index 0aeda502..e357f48e 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/PickListType.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListType.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist enum class PickListType { FAVORITE, CREATED diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/PickListUiState.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt similarity index 85% rename from app/src/main/java/com/squirtles/musicroad/picklist/PickListUiState.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt index 3961e0ef..f0df5a8c 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/PickListUiState.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListUiState.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist import com.squirtles.domain.model.Order import com.squirtles.domain.model.Pick diff --git a/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt new file mode 100644 index 00000000..76efe0d2 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/PickListViewModel.kt @@ -0,0 +1,105 @@ +package com.squirtles.musicroad.common.picklist + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.squirtles.domain.model.Order +import com.squirtles.domain.model.Pick +import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface +import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface +import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +abstract class PickListViewModel( + val fetchPickListUseCase: FetchPickListUseCaseInterface, + val getPickListOrderUseCase: GetPickListOrderUseCaseInterface, + val savePickListOrderUseCase: SavePickListOrderUseCaseInterface, + val removePickUseCase: DeletePickListUseCaseInterface, + val getCurrentUserUseCase: GetCurrentUserUseCase +) : ViewModel() { + + private var pickList: List = emptyList() + + private val _pickListUiState = MutableStateFlow(PickListUiState.Loading) + val pickListUiState = _pickListUiState.asStateFlow() + + private val _selectedPicksId = MutableStateFlow>(emptySet()) + val selectedPicksId = _selectedPicksId.asStateFlow() + + fun fetchPickList(userId: String) { + viewModelScope.launch { + fetchPickListUseCase(userId) + .onSuccess { picks -> + pickList = picks + sortPickList(getPickListOrderUseCase()) + } + .onFailure { + _pickListUiState.emit(PickListUiState.Error) + } + } + } + + private fun sortPickList(order: Order) { + _pickListUiState.value = PickListUiState.Success( + pickList = pickList.setOrderedList(order), + order = order + ) + } + + fun setListOrder(order: Order) { + viewModelScope.launch { + savePickListOrderUseCase(order) + sortPickList(order) + } + } + + fun toggleSelectedPick(pickId: String) { + val curSelectedPicksId = _selectedPicksId.value + _selectedPicksId.value = + if (curSelectedPicksId.contains(pickId)) curSelectedPicksId - pickId else curSelectedPicksId + pickId + } + + fun selectAllPicks() { + _selectedPicksId.value = pickList.map { it.id }.toSet() + } + + fun deselectAllPicks() { + _selectedPicksId.value = emptySet() + } + + fun deleteSelectedPicks(userId: String) { + viewModelScope.launch { + _pickListUiState.value = PickListUiState.Loading + + val deleteJobList = _selectedPicksId.value.map { pickId -> + async { removePickUseCase(pickId, userId) } + }.awaitAll() + + deselectAllPicks() + + if (deleteJobList.all { it.isSuccess }) { + fetchPickList(userId) + } else { + _pickListUiState.value = PickListUiState.Error + Log.e("PickListViewModel", "[픽 목록] 다중 삭제 오류") + } + } + } + + private fun List.setOrderedList(order: Order): List { + return if (pickList.isEmpty()) this + else when (order) { + Order.LATEST -> this + Order.OLDEST -> this.reversed() + Order.FAVORITE_DESC -> this.sortedByDescending { it.favoriteCount } + } + } + + fun getUserId() = getCurrentUserUseCase()?.userId +} diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/DeleteSelectedPickDialog.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt similarity index 92% rename from app/src/main/java/com/squirtles/musicroad/picklist/DeleteSelectedPickDialog.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt index e694494d..bd83d6b7 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/DeleteSelectedPickDialog.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/DeleteSelectedPickDialog.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist.components import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource @@ -7,6 +7,7 @@ import com.squirtles.musicroad.R import com.squirtles.musicroad.common.DialogTextButton import com.squirtles.musicroad.common.HorizontalSpacer import com.squirtles.musicroad.common.MessageAlertDialog +import com.squirtles.musicroad.common.picklist.PickListType import com.squirtles.musicroad.ui.theme.Primary @Composable diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/EditModeAction.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt similarity index 96% rename from app/src/main/java/com/squirtles/musicroad/picklist/EditModeAction.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt index aa8c750d..e904a8dd 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/EditModeAction.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeAction.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/EditModeBottomButton.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/picklist/EditModeBottomButton.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt index 8c635b5b..a8014244 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/EditModeBottomButton.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/EditModeBottomButton.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist.components import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/OrderBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/picklist/OrderBottomSheet.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt index 70170130..401c0997 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/OrderBottomSheet.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/OrderBottomSheet.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/PickItem.kt b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt similarity index 94% rename from app/src/main/java/com/squirtles/musicroad/picklist/PickItem.kt rename to app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt index 0713411d..54812bad 100644 --- a/app/src/main/java/com/squirtles/musicroad/picklist/PickItem.kt +++ b/app/src/main/java/com/squirtles/musicroad/common/picklist/components/PickItem.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.picklist +package com.squirtles.musicroad.common.picklist.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -66,7 +66,10 @@ internal fun PickItem( verticalAlignment = Alignment.CenterVertically ) { AlbumImage( - imageUrl = song.getImageUrlWithSize(Constants.REQUEST_IMAGE_SIZE_DEFAULT), + imageUrl = song.getImageUrlWithSize( + Constants.REQUEST_IMAGE_SIZE_DEFAULT.width, + Constants.REQUEST_IMAGE_SIZE_DEFAULT.height + ), modifier = Modifier .size(64.dp) .clip(RoundedCornerShape(8.dp)) diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt index 18de4460..21c7bbb6 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickScreen.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.toColorInt import androidx.core.view.WindowInsetsControllerCompat +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.squirtles.domain.model.Song import com.squirtles.musicroad.R @@ -66,11 +67,12 @@ import com.squirtles.musicroad.ui.theme.White @Composable fun CreatePickScreen( - createPickViewModel: CreatePickViewModel, + song: Song, onBackClick: () -> Unit, onCreateClick: (String) -> Unit, + createPickViewModel: CreatePickViewModel = hiltViewModel(), ) { - val song = createPickViewModel.selectedSong ?: DEFAULT_SONG + val comment = createPickViewModel.comment.collectAsStateWithLifecycle() val uiState by createPickViewModel.createPickUiState.collectAsStateWithLifecycle() var showCreateIndicator by rememberSaveable { mutableStateOf(false) } @@ -217,7 +219,7 @@ private fun CreatePickContent( ) AlbumImage( - imageUrl = song.getImageUrlWithSize(RequestImageSize), + imageUrl = song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), modifier = Modifier .fillMaxWidth() .padding(horizontal = 30.dp) diff --git a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt index 678faa52..1e985d17 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/create/CreatePickViewModel.kt @@ -2,57 +2,40 @@ package com.squirtles.musicroad.create import android.location.Location import android.util.Log +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.paging.PagingData -import androidx.paging.cachedIn +import androidx.navigation.toRoute import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.local.FetchLastLocationUseCase -import com.squirtles.domain.usecase.local.GetCurrentUserUseCase +import com.squirtles.domain.usecase.location.GetLastLocationUseCase import com.squirtles.domain.usecase.music.FetchMusicVideoUseCase -import com.squirtles.domain.usecase.music.SearchSongsUseCase import com.squirtles.domain.usecase.mypick.CreatePickUseCase -import com.squirtles.musicroad.common.throttleFirst +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.musicroad.navigation.SearchRoute +import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import javax.inject.Inject @OptIn(FlowPreview::class) @HiltViewModel class CreatePickViewModel @Inject constructor( - fetchLastLocationUseCase: FetchLastLocationUseCase, - private val searchSongsUseCase: SearchSongsUseCase, + savedStateHandle: SavedStateHandle, + getLastLocationUseCase: GetLastLocationUseCase, private val fetchMusicVideoUseCase: FetchMusicVideoUseCase, private val createPickUseCase: CreatePickUseCase, private val getCurrentUserUseCase: GetCurrentUserUseCase ) : ViewModel() { - // SearchMusicScreen - private val _searchUiState = MutableStateFlow(SearchUiState.HotResult) - val searchUiState = _searchUiState.asStateFlow() + private val song = savedStateHandle.toRoute(SearchRoute.Create.typeMap).song - private val _searchResult = MutableStateFlow>(PagingData.empty()) - val searchResult = _searchResult.asStateFlow() - - private var _selectedSong: Song? = null - val selectedSong get() = _selectedSong - - private val _searchText = MutableStateFlow("") - val searchText = _searchText.asStateFlow() - - private var searchJob: Job? = null - - // CreatePickScreen private val _createPickUiState = MutableStateFlow>(CreateUiState.Default) val createPickUiState = _createPickUiState.asStateFlow() @@ -65,47 +48,21 @@ class CreatePickViewModel @Inject constructor( init { // 데이터소스의 위치값을 계속 collect하며 curLocation 변수에 저장 viewModelScope.launch { - fetchLastLocationUseCase().collect { location -> + getLastLocationUseCase().collect { location -> lastLocation = location } } - viewModelScope.launch { - _searchText - .debounce(300) - .collect { searchKeyword -> - searchJob?.cancel() - if (searchKeyword.isNotBlank()) { - searchJob = launch { searchSongs(searchKeyword) } - } else { - _searchUiState.emit(SearchUiState.HotResult) - } - } - } - // 등록 버튼 클릭 후 3초 이내의 클릭은 무시하고 픽 생성하기 viewModelScope.launch { createPickClick .throttleFirst(3000) .collect { - createPick() + createPick(song) } } } - private suspend fun searchSongs(searchKeyword: String) { - searchSongsUseCase(searchKeyword) - .cachedIn(viewModelScope) - .collectLatest { - _searchResult.emit(it) - _searchUiState.emit(SearchUiState.SearchResult) - } - } - - fun onSongItemClick(song: Song) { - _selectedSong = song - } - fun onCommentChange(text: String) { _comment.value = text } @@ -114,50 +71,45 @@ class CreatePickViewModel @Inject constructor( _comment.value = "" } - fun onSearchTextChange(text: String) { - _searchText.value = text - } - fun onCreatePickClick() { viewModelScope.launch { createPickClick.emit(Unit) } } - private fun createPick() { - _selectedSong?.let { song -> - viewModelScope.launch { - if (lastLocation == null) { - /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ - return@launch - } - - val musicVideo = fetchMusicVideoUseCase(song) + private fun createPick(song: Song) { + viewModelScope.launch { + if (lastLocation == null) { + /* TODO: DEFAULT 인 경우 -> LocalDataSource 위치 데이터 못 불러옴 */ + return@launch + } - /* 등록 결과 - pick ID 담긴 Result */ - getCurrentUserUseCase()?.let { user -> - val createResult = createPickUseCase( - Pick( - id = "", - song = song, - comment = _comment.value, - createdAt = "", - createdBy = Creator( - userId = user.userId, - userName = user.userName - ), - location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), - musicVideoUrl = musicVideo?.previewUrl ?: "", - musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" - ) + val musicVideo = fetchMusicVideoUseCase(song) + + /* 등록 결과 - pick ID 담긴 Result */ + getCurrentUserUseCase()?.let { user -> + val createResult = createPickUseCase( + Pick( + id = "", + song = song, + comment = _comment.value, + createdAt = "", + createdBy = Creator( + userId = user.userId, + userName = user.userName + ), + location = LocationPoint(lastLocation!!.latitude, lastLocation!!.longitude), + musicVideoUrl = musicVideo?.previewUrl ?: "", + musicVideoThumbnailUrl = musicVideo?.thumbnailUrl ?: "" ) - createResult.onSuccess { pickId -> - _createPickUiState.emit(CreateUiState.Success(pickId)) - }.onFailure { - /* TODO: Firestore 등록 실패처리 */ - _createPickUiState.emit(CreateUiState.Error) - Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) - } + ) + + createResult.onSuccess { pickId -> + _createPickUiState.emit(CreateUiState.Success(pickId)) + }.onFailure { + /* TODO: Firestore 등록 실패처리 */ + _createPickUiState.emit(CreateUiState.Error) + Log.d("CreatePickViewModel", createResult.exceptionOrNull()?.message.toString()) } } } diff --git a/app/src/main/java/com/squirtles/musicroad/pick/PickViewModel.kt b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt similarity index 77% rename from app/src/main/java/com/squirtles/musicroad/pick/PickViewModel.kt rename to app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt index 47fc63b7..761ebe70 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/PickViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/DetailViewModel.kt @@ -1,19 +1,20 @@ -package com.squirtles.musicroad.pick +package com.squirtles.musicroad.detail import androidx.core.graphics.toColorInt +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.Creator import com.squirtles.domain.model.LocationPoint import com.squirtles.domain.model.Pick import com.squirtles.domain.model.Song -import com.squirtles.domain.usecase.favoritepick.CreateFavoriteUseCase -import com.squirtles.domain.usecase.favoritepick.DeleteFavoriteUseCase -import com.squirtles.domain.usecase.local.GetCurrentUserUseCase +import com.squirtles.domain.usecase.favorite.CreateFavoriteUseCase +import com.squirtles.domain.usecase.favorite.DeleteFavoriteUseCase import com.squirtles.domain.usecase.mypick.DeletePickUseCase import com.squirtles.domain.usecase.pick.FetchIsFavoriteUseCase import com.squirtles.domain.usecase.pick.FetchPickUseCase -import com.squirtles.musicroad.common.throttleFirst +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.musicroad.utils.throttleFirst import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow @@ -23,22 +24,19 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -enum class FavoriteAction { - ADDED, DELETED -} - @HiltViewModel -class PickViewModel @Inject constructor( +class DetailViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, private val fetchPickUseCase: FetchPickUseCase, + private val fetchIsFavoriteUseCase: FetchIsFavoriteUseCase, private val getCurrentUserUseCase: GetCurrentUserUseCase, private val deletePickUseCase: DeletePickUseCase, - private val fetchIsFavoriteUseCase: FetchIsFavoriteUseCase, private val createFavoriteUseCase: CreateFavoriteUseCase, private val deleteFavoriteUseCase: DeleteFavoriteUseCase, ) : ViewModel() { - private val _detailPickUiState = MutableStateFlow(DetailPickUiState.Loading) - val detailPickUiState = _detailPickUiState.asStateFlow() + private val _pickDetailUiState = MutableStateFlow(PickDetailUiState.Loading) + val pickDetailUiState = _pickDetailUiState.asStateFlow() private var _currentTab = DETAIL_PICK_TAB val currentTab get() = _currentTab @@ -68,7 +66,7 @@ class PickViewModel @Inject constructor( fun fetchPick(pickId: String) { viewModelScope.launch { - _detailPickUiState.emit(DetailPickUiState.Loading) + _pickDetailUiState.emit(PickDetailUiState.Loading) val fetchPick = async { fetchPickUseCase(pickId) } @@ -79,8 +77,8 @@ class PickViewModel @Inject constructor( when { fetchPickResult.isSuccess && fetchIsFavoriteResult.isSuccess -> { - _detailPickUiState.emit( - DetailPickUiState.Success( + _pickDetailUiState.emit( + PickDetailUiState.Success( pick = fetchPickResult.getOrDefault(DEFAULT_PICK), isFavorite = fetchIsFavoriteResult.getOrDefault(false) ) @@ -88,7 +86,7 @@ class PickViewModel @Inject constructor( } else -> { - _detailPickUiState.emit(DetailPickUiState.Error) + _pickDetailUiState.emit(PickDetailUiState.Error) } } } @@ -103,13 +101,13 @@ class PickViewModel @Inject constructor( fun deletePick(pickId: String) { viewModelScope.launch { getUserId()?.let { userId -> - _detailPickUiState.emit(DetailPickUiState.Loading) + _pickDetailUiState.emit(PickDetailUiState.Loading) deletePickUseCase(pickId, userId) .onSuccess { - _detailPickUiState.emit(DetailPickUiState.Deleted) + _pickDetailUiState.emit(PickDetailUiState.Deleted) } .onFailure { - _detailPickUiState.emit(DetailPickUiState.Error) + _pickDetailUiState.emit(PickDetailUiState.Error) } } } @@ -120,13 +118,13 @@ class PickViewModel @Inject constructor( createFavoriteUseCase(pickId, userId) .onSuccess { _favoriteAction.emit(FavoriteAction.ADDED) - val currentUiState = _detailPickUiState.value as? DetailPickUiState.Success + val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success currentUiState?.let { successState -> - _detailPickUiState.emit(successState.copy(isFavorite = true)) + _pickDetailUiState.emit(successState.copy(isFavorite = true)) } } .onFailure { - _detailPickUiState.emit(DetailPickUiState.Error) + _pickDetailUiState.emit(PickDetailUiState.Error) } } } @@ -136,13 +134,13 @@ class PickViewModel @Inject constructor( deleteFavoriteUseCase(pickId, userId) .onSuccess { _favoriteAction.emit(FavoriteAction.DELETED) - val currentUiState = _detailPickUiState.value as? DetailPickUiState.Success + val currentUiState = _pickDetailUiState.value as? PickDetailUiState.Success currentUiState?.let { successState -> - _detailPickUiState.emit(successState.copy(isFavorite = false)) + _pickDetailUiState.emit(successState.copy(isFavorite = false)) } } .onFailure { - _detailPickUiState.emit(DetailPickUiState.Error) + _pickDetailUiState.emit(PickDetailUiState.Error) } } } diff --git a/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt b/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt new file mode 100644 index 00000000..8169095b --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/detail/FavoriteAction.kt @@ -0,0 +1,5 @@ +package com.squirtles.musicroad.detail + +enum class FavoriteAction { + ADDED, DELETED +} diff --git a/app/src/main/java/com/squirtles/musicroad/pick/DetailPickScreen.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt similarity index 88% rename from app/src/main/java/com/squirtles/musicroad/pick/DetailPickScreen.kt rename to app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt index 851d837d..51515725 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/DetailPickScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick +package com.squirtles.musicroad.detail import android.app.Activity import android.content.Context @@ -63,16 +63,16 @@ import com.squirtles.musicroad.common.HorizontalSpacer import com.squirtles.musicroad.common.MessageAlertDialog import com.squirtles.musicroad.common.SignInAlertDialog import com.squirtles.musicroad.common.VerticalSpacer +import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK +import com.squirtles.musicroad.detail.components.CircleAlbumCover +import com.squirtles.musicroad.detail.components.CommentText +import com.squirtles.musicroad.detail.components.DetailPickTopAppBar +import com.squirtles.musicroad.detail.components.MusicVideoKnob +import com.squirtles.musicroad.detail.components.PickInformation +import com.squirtles.musicroad.detail.components.SongInfo +import com.squirtles.musicroad.detail.components.music.MusicPlayer +import com.squirtles.musicroad.detail.components.music.visualizer.BaseVisualizer import com.squirtles.musicroad.media.PlayerServiceViewModel -import com.squirtles.musicroad.pick.PickViewModel.Companion.DEFAULT_PICK -import com.squirtles.musicroad.pick.components.CircleAlbumCover -import com.squirtles.musicroad.pick.components.CommentText -import com.squirtles.musicroad.pick.components.DetailPickTopAppBar -import com.squirtles.musicroad.pick.components.MusicVideoKnob -import com.squirtles.musicroad.pick.components.PickInformation -import com.squirtles.musicroad.pick.components.SongInfo -import com.squirtles.musicroad.pick.components.music.MusicPlayer -import com.squirtles.musicroad.pick.components.music.visualizer.BaseVisualizer import com.squirtles.musicroad.ui.theme.Black import com.squirtles.musicroad.ui.theme.Primary import com.squirtles.musicroad.ui.theme.White @@ -81,18 +81,18 @@ import kotlinx.coroutines.launch import kotlin.math.absoluteValue @Composable -fun DetailPickScreen( +fun PickDetailScreen( pickId: String, - playerServiceViewModel: PlayerServiceViewModel, - onProfileClick: (String) -> Unit, + onUserInfoClick: (String) -> Unit, onBackClick: () -> Unit, onDeleted: (Context) -> Unit, - pickViewModel: PickViewModel = hiltViewModel(), + playerServiceViewModel: PlayerServiceViewModel, + detailViewModel: DetailViewModel = hiltViewModel(), accountViewModel: AccountViewModel = hiltViewModel() ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current - val uiState by pickViewModel.detailPickUiState.collectAsStateWithLifecycle() + val uiState by detailViewModel.pickDetailUiState.collectAsStateWithLifecycle() var showDeletePickDialog by rememberSaveable { mutableStateOf(false) } var showProcessIndicator by rememberSaveable { mutableStateOf(false) } var isMusicVideoAvailable by remember { mutableStateOf(false) } @@ -108,16 +108,17 @@ fun DetailPickScreen( } LaunchedEffect(Unit) { - pickViewModel.fetchPick(pickId) + detailViewModel.fetchPick(pickId) + accountViewModel.signInSuccess .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) .collect { isSuccess -> - if (isSuccess) pickViewModel.fetchPick(pickId) + if (isSuccess) detailViewModel.fetchPick(pickId) } } when (uiState) { - DetailPickUiState.Loading -> { + PickDetailUiState.Loading -> { Box( modifier = Modifier .fillMaxSize() @@ -128,11 +129,12 @@ fun DetailPickScreen( } } - is DetailPickUiState.Success -> { - val pick = (uiState as DetailPickUiState.Success).pick - val isFavorite = (uiState as DetailPickUiState.Success).isFavorite - val isNonMember = pickViewModel.getUserId() == null - val isCreatedBySelf = pickViewModel.getUserId() == pick.createdBy.userId + is PickDetailUiState.Success -> { + val lifecycleOwner = LocalLifecycleOwner.current + val pick = (uiState as PickDetailUiState.Success).pick + val isFavorite = (uiState as PickDetailUiState.Success).isFavorite + val isNonMember = detailViewModel.getUserId() == null + val isCreatedBySelf = detailViewModel.getUserId() == pick.createdBy.userId var favoriteCount by rememberSaveable { mutableIntStateOf(pick.favoriteCount) } val onActionClick: () -> Unit = { when { @@ -148,7 +150,7 @@ fun DetailPickScreen( isFavorite -> { showProcessIndicator = true - pickViewModel.toggleFavoritePick( + detailViewModel.toggleFavoritePick( pickId = pickId, isAdding = false ) @@ -156,7 +158,7 @@ fun DetailPickScreen( else -> { showProcessIndicator = true - pickViewModel.toggleFavoritePick( + detailViewModel.toggleFavoritePick( pickId = pickId, isAdding = true ) @@ -170,7 +172,7 @@ fun DetailPickScreen( ) LaunchedEffect(Unit) { - pickViewModel.favoriteAction + detailViewModel.favoriteAction .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) .collect { action -> when (action) { @@ -197,12 +199,12 @@ fun DetailPickScreen( } LaunchedEffect(pagerState) { - pagerState.scrollToPage(page = pickViewModel.currentTab) + pagerState.scrollToPage(page = detailViewModel.currentTab) } DisposableEffect(Unit) { onDispose { - pickViewModel.setCurrentTab(pagerState.currentPage) + detailViewModel.setCurrentTab(pagerState.currentPage) } } @@ -211,15 +213,15 @@ fun DetailPickScreen( ) { page -> when (page) { DETAIL_PICK_TAB -> { - DetailPick( + PickDetailContents( pick = pick, - currentUserId = pickViewModel.getUserId(), + currentUserId = detailViewModel.getUserId(), isFavorite = isFavorite, pickUserId = pick.createdBy.userId, pickUserName = pick.createdBy.userName, favoriteCount = favoriteCount, isMusicVideoAvailable = isMusicVideoAvailable, - onProfileClick = onProfileClick, + onUserInfoClick = onUserInfoClick, playerServiceViewModel = playerServiceViewModel, onBackClick = { onBackClick() @@ -258,7 +260,7 @@ fun DetailPickScreen( } } - DetailPickUiState.Deleted -> { + PickDetailUiState.Deleted -> { LaunchedEffect(Unit) { onBackClick() onDeleted(context) @@ -270,7 +272,7 @@ fun DetailPickScreen( } } - DetailPickUiState.Error -> { + PickDetailUiState.Error -> { LaunchedEffect(Unit) { Toast.makeText( context, @@ -280,7 +282,7 @@ fun DetailPickScreen( } // Show default pick - DetailPick( + PickDetailContents( pick = DEFAULT_PICK, currentUserId = null, isFavorite = false, @@ -289,7 +291,7 @@ fun DetailPickScreen( favoriteCount = 0, isMusicVideoAvailable = false, playerServiceViewModel = playerServiceViewModel, - onProfileClick = onProfileClick, + onUserInfoClick = onUserInfoClick, onBackClick = onBackClick, onActionClick = { } ) @@ -316,7 +318,7 @@ fun DetailPickScreen( DialogTextButton( onClick = { showDeletePickDialog = false - pickViewModel.deletePick(pickId) + detailViewModel.deletePick(pickId) }, text = stringResource(R.string.delete_pick_dialog_delete), textColor = Primary, @@ -359,7 +361,7 @@ fun DetailPickScreen( } @Composable -private fun DetailPick( +private fun PickDetailContents( pick: Pick, isFavorite: Boolean, currentUserId: String?, @@ -368,7 +370,7 @@ private fun DetailPick( favoriteCount: Int, isMusicVideoAvailable: Boolean, playerServiceViewModel: PlayerServiceViewModel, - onProfileClick: (String) -> Unit, + onUserInfoClick: (String) -> Unit, onBackClick: () -> Unit, onActionClick: () -> Unit ) { @@ -377,7 +379,6 @@ private fun DetailPick( val dynamicBackgroundColor = Color(pick.song.bgColor) val onDynamicBackgroundColor = if (dynamicBackgroundColor.luminance() >= 0.5f) Black else White val view = LocalView.current - val context = LocalContext.current val baseVisualizer = remember { BaseVisualizer() } @@ -409,7 +410,7 @@ private fun DetailPick( userId = pickUserId, userName = pickUserName, onDynamicBackgroundColor = onDynamicBackgroundColor, - onProfileClick = onProfileClick, + onUserInfoClick = onUserInfoClick, onBackClick = { onBackClick() }, @@ -519,8 +520,8 @@ fun Context.showShortToast(message: String) { @Preview @Composable -private fun DetailPickPreview() { - DetailPick( +private fun PickDetailPreview() { + PickDetailContents( pick = DEFAULT_PICK, currentUserId = null, isFavorite = false, @@ -528,7 +529,7 @@ private fun DetailPickPreview() { pickUserName = "짱구", favoriteCount = 0, isMusicVideoAvailable = true, - onProfileClick = {}, + onUserInfoClick = {}, playerServiceViewModel = hiltViewModel(), onBackClick = {}, onActionClick = {}, diff --git a/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt new file mode 100644 index 00000000..a55c5b86 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/detail/PickDetailUiState.kt @@ -0,0 +1,10 @@ +package com.squirtles.musicroad.detail + +import com.squirtles.domain.model.Pick + +sealed class PickDetailUiState { + data object Loading : PickDetailUiState() + data class Success(val pick: Pick, val isFavorite: Boolean) : PickDetailUiState() + data object Deleted : PickDetailUiState() + data object Error : PickDetailUiState() +} diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/CircleAlbumCover.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt similarity index 87% rename from app/src/main/java/com/squirtles/musicroad/pick/components/CircleAlbumCover.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt index a313f7ea..ab5a4868 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/CircleAlbumCover.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/CircleAlbumCover.kt @@ -1,6 +1,5 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components -import android.util.Size import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize @@ -18,8 +17,8 @@ import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.squirtles.domain.model.Song import com.squirtles.musicroad.R -import com.squirtles.musicroad.pick.components.music.visualizer.BaseVisualizer -import com.squirtles.musicroad.pick.components.music.visualizer.CircleVisualizer +import com.squirtles.musicroad.detail.components.music.visualizer.BaseVisualizer +import com.squirtles.musicroad.detail.components.music.visualizer.CircleVisualizer @Composable internal fun CircleAlbumCover( @@ -57,7 +56,7 @@ internal fun CircleAlbumCover( ) AsyncImage( - model = song.getImageUrlWithSize(Size(400, 400)), + model = song.getImageUrlWithSize(400, 400), contentDescription = song.albumName + stringResource(id = R.string.pick_album_description), modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/CommentText.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/pick/components/CommentText.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt index ea53839c..bf3a830e 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/CommentText.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/CommentText.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/DetailPickTopAppBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt similarity index 96% rename from app/src/main/java/com/squirtles/musicroad/pick/components/DetailPickTopAppBar.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt index 481b29e5..67dbbb9a 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/DetailPickTopAppBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/DetailPickTopAppBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -32,7 +32,7 @@ fun DetailPickTopAppBar( userId: String, userName: String, onDynamicBackgroundColor: Color, - onProfileClick: (String) -> Unit, + onUserInfoClick: (String) -> Unit, onBackClick: () -> Unit, onActionClick: () -> Unit, ) { @@ -42,7 +42,7 @@ fun DetailPickTopAppBar( modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, - onClick = { onProfileClick(userId) } + onClick = { onUserInfoClick(userId) } ), verticalAlignment = Alignment.CenterVertically ) { diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/MusicVideoKnob.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/pick/components/MusicVideoKnob.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt index 19ad7557..8d7ca3b0 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/MusicVideoKnob.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/MusicVideoKnob.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import android.util.Size import androidx.compose.animation.core.FastOutSlowInEasing diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/PickInformation.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt similarity index 96% rename from app/src/main/java/com/squirtles/musicroad/pick/components/PickInformation.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt index 0a1ed0b2..51a6b925 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/PickInformation.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PickInformation.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/PlayCircularProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/pick/components/PlayCircularProgressIndicator.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt index e841107c..d9d668a0 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/PlayCircularProgressIndicator.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/PlayCircularProgressIndicator.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/SongInfo.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt similarity index 95% rename from app/src/main/java/com/squirtles/musicroad/pick/components/SongInfo.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt index d289dc43..149104a5 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/SongInfo.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/SongInfo.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/SwipeUpIcon.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt similarity index 95% rename from app/src/main/java/com/squirtles/musicroad/pick/components/SwipeUpIcon.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt index 8b0665a6..c9b4f063 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/SwipeUpIcon.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/SwipeUpIcon.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components +package com.squirtles.musicroad.detail.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/MusicPlayer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/MusicPlayer.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt index 1497778b..8f49325b 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/MusicPlayer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/MusicPlayer.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music +package com.squirtles.musicroad.detail.components.music import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayBar.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayBar.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt index 2e4bb714..e4d91cf3 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayBar.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayBar.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music +package com.squirtles.musicroad.detail.components.music import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayProgressIndicator.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt similarity index 96% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayProgressIndicator.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt index 216f4506..b0586e02 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayProgressIndicator.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayProgressIndicator.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music +package com.squirtles.musicroad.detail.components.music import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayerControls.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayerControls.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt index 889b5bee..d24f0d45 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/PlayerControls.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/PlayerControls.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music +package com.squirtles.musicroad.detail.components.music import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/BaseVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/BaseVisualizer.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt index 0be896a6..341d7e77 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/BaseVisualizer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/BaseVisualizer.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music.visualizer +package com.squirtles.musicroad.detail.components.music.visualizer import android.media.audiofx.Visualizer import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CanvasCircleVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CanvasCircleVisualizer.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt index efe632c8..12d9b634 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CanvasCircleVisualizer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CanvasCircleVisualizer.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music.visualizer +package com.squirtles.musicroad.detail.components.music.visualizer import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxSize diff --git a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CircleVisualizer.kt b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt similarity index 98% rename from app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CircleVisualizer.kt rename to app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt index 932713c6..39dd6a9b 100644 --- a/app/src/main/java/com/squirtles/musicroad/pick/components/music/visualizer/CircleVisualizer.kt +++ b/app/src/main/java/com/squirtles/musicroad/detail/components/music/visualizer/CircleVisualizer.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.pick.components.music.visualizer +package com.squirtles.musicroad.detail.components.music.visualizer import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt new file mode 100644 index 00000000..5a29bc8c --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteListViewModel.kt @@ -0,0 +1,25 @@ +package com.squirtles.musicroad.favorite + +import com.squirtles.domain.usecase.favorite.DeleteFavoriteUseCase +import com.squirtles.domain.usecase.favorite.FetchFavoritePicksUseCase +import com.squirtles.domain.usecase.order.GetFavoriteListOrderUseCase +import com.squirtles.domain.usecase.order.SaveFavoriteListOrderUseCase +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.musicroad.common.picklist.PickListViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class FavoriteListViewModel @Inject constructor( + fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, + getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase, + saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase, + deleteFavoriteUseCase: DeleteFavoriteUseCase, + getCurrentUserUseCase: GetCurrentUserUseCase +) : PickListViewModel( + fetchPickListUseCase = fetchFavoritePicksUseCase, + getPickListOrderUseCase = getFavoriteListOrderUseCase, + savePickListOrderUseCase = saveFavoriteListOrderUseCase, + removePickUseCase = deleteFavoriteUseCase, + getCurrentUserUseCase = getCurrentUserUseCase +) diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt new file mode 100644 index 00000000..d3668042 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/favorite/FavoriteScreen.kt @@ -0,0 +1,47 @@ +package com.squirtles.musicroad.favorite + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.musicroad.common.picklist.PickListType +import com.squirtles.musicroad.picklist.PickListScreenContents + +@Composable +fun FavoriteScreen( + userId: String, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + favoriteListViewModel: FavoriteListViewModel = hiltViewModel() +) { + val uiState by favoriteListViewModel.pickListUiState.collectAsStateWithLifecycle() + val selectedPicksId by favoriteListViewModel.selectedPicksId.collectAsStateWithLifecycle() + var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(Unit) { + favoriteListViewModel.fetchPickList(userId) + } + + PickListScreenContents( + userId = userId, + showOrderBottomSheet = showOrderBottomSheet, + selectedPicksId = selectedPicksId, + pickListType = PickListType.FAVORITE, + uiState = uiState, + onBackClick = onBackClick, + onItemClick = onItemClick, + setListOrder = favoriteListViewModel::setListOrder, + setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, + selectAllPicks = favoriteListViewModel::selectAllPicks, + deselectAllPicks = favoriteListViewModel::deselectAllPicks, + toggleSelectedPick = favoriteListViewModel::toggleSelectedPick, + deleteSelectedPicks = favoriteListViewModel::deleteSelectedPicks, + getUserId = { + favoriteListViewModel.getUserId().toString() + } + ) +} diff --git a/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt b/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt new file mode 100644 index 00000000..c78a57fe --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/favorite/navigation/FavoriteNavigation.kt @@ -0,0 +1,28 @@ +package com.squirtles.musicroad.favorite.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.musicroad.favorite.FavoriteScreen +import com.squirtles.musicroad.navigation.MainRoute + +fun NavController.navigateFavorite(userId: String, navOptions: NavOptions? = null) { + navigate(MainRoute.Favorite(userId), navOptions) +} + +fun NavGraphBuilder.favoriteNavGraph( + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, +) { + composable { backStackEntry -> + val userId = backStackEntry.toRoute().userId + + FavoriteScreen( + userId = userId, + onBackClick = onBackClick, + onItemClick = onItemClick, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt b/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt new file mode 100644 index 00000000..11e27911 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/main/LoadingState.kt @@ -0,0 +1,9 @@ +package com.squirtles.musicroad.main + +sealed class LoadingState { + data object Loading : LoadingState() + data class Success(val userId: String?) : LoadingState() + data class NetworkError(val error: String) : LoadingState() + data class CreatedUserError(val error: String) : LoadingState() + data class UserNotFoundError(val error: String) : LoadingState() +} diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt index f6fe243b..84b8dffe 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainActivity.kt @@ -25,8 +25,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.compose.rememberNavController import com.squirtles.musicroad.R -import com.squirtles.musicroad.main.navigations.MainNavGraph -import com.squirtles.musicroad.main.navigations.MainNavigationActions +import com.squirtles.musicroad.main.navigation.MainNavHost +import com.squirtles.musicroad.main.navigation.MainNavigator +import com.squirtles.musicroad.main.navigation.rememberMainNavigator import com.squirtles.musicroad.ui.theme.MusicRoadTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.cancel @@ -136,14 +137,13 @@ class MainActivity : AppCompatActivity() { private fun setMusicRoadContent() { setContent { + val navigator: MainNavigator = rememberMainNavigator() + MusicRoadTheme { val navController = rememberNavController() - val navigationActions = remember(navController) { - MainNavigationActions(navController) - } - MainNavGraph( - navController = navController, - navigationActions = navigationActions + + MainNavHost( + navigator = navigator, ) } } diff --git a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt index e5635bfa..ad8de40b 100644 --- a/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/main/MainViewModel.kt @@ -2,26 +2,18 @@ package com.squirtles.musicroad.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.squirtles.domain.exception.FirebaseException -import com.squirtles.domain.usecase.local.GetUserIdFromLocalStorageUseCase +import com.squirtles.domain.firebase.FirebaseException import com.squirtles.domain.usecase.user.FetchUserUseCase +import com.squirtles.domain.usecase.user.GetUserIdFromDataStoreUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject -sealed class LoadingState { - data object Loading : LoadingState() - data class Success(val userId: String?) : LoadingState() - data class NetworkError(val error: String) : LoadingState() - data class CreatedUserError(val error: String) : LoadingState() - data class UserNotFoundError(val error: String) : LoadingState() -} - @HiltViewModel class MainViewModel @Inject constructor( - getUserIdFromLocalStorageUseCase: GetUserIdFromLocalStorageUseCase, + getUserIdFromDataStoreUseCase: GetUserIdFromDataStoreUseCase, private val fetchUserUseCase: FetchUserUseCase, ) : ViewModel() { @@ -31,7 +23,7 @@ class MainViewModel @Inject constructor( private var _canRequestPermission = true val canRequestPermission get() = _canRequestPermission - private val _localUserId = getUserIdFromLocalStorageUseCase() + private val _localUserId = getUserIdFromDataStoreUseCase() init { viewModelScope.launch { diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt new file mode 100644 index 00000000..f546f96b --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavHost.kt @@ -0,0 +1,59 @@ +package com.squirtles.musicroad.main.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.compose.NavHost +import com.squirtles.musicroad.favorite.navigation.favoriteNavGraph +import com.squirtles.musicroad.map.MapViewModel +import com.squirtles.musicroad.map.navigation.mapNavGraph +import com.squirtles.musicroad.media.PlayerServiceViewModel +import com.squirtles.musicroad.search.navigation.searchNavGraph +import com.squirtles.musicroad.userinfo.navigation.userInfoNavGraph + +@Composable +internal fun MainNavHost( + modifier: Modifier = Modifier, + navigator: MainNavigator, + mapViewModel: MapViewModel = hiltViewModel(), + playerServiceViewModel: PlayerServiceViewModel = hiltViewModel(), +) { + NavHost( + navController = navigator.navController, + startDestination = navigator.startDestination, + ) { + mapNavGraph( + mapViewModel = mapViewModel, + playerServiceViewModel = playerServiceViewModel, + onFavoriteClick = navigator::navigateFavorite, + onCenterClick = navigator::navigateSearch, + onUserInfoClick = navigator::navigateUserInfo, + onPickSummaryClick = navigator::navigatePickDetail, + onBackClick = navigator::popBackStackIfNotMap, + onDeleted = mapViewModel::resetClickedMarkerState + ) + + searchNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigateCreate, + onCreateClick = { pickId -> + navigator.navigatePickDetail(pickId, true) + }, + ) + + favoriteNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigatePickDetail + ) + + userInfoNavGraph( + onBackClick = navigator::popBackStackIfNotMap, + onItemClick = navigator::navigatePickDetail, + onBackToMapClick = navigator::navigateMap, + onFavoritePicksClick = navigator::navigateFavorite, + onMyPicksClick = navigator::navigateMyPicks, + onEditProfileClick = navigator::navigateEditProfile, + onEditNotificationClick = navigator::navigateEditNotificationSetting, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt new file mode 100644 index 00000000..a29728fb --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/main/navigation/MainNavigator.kt @@ -0,0 +1,126 @@ +package com.squirtles.musicroad.main.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navOptions +import com.squirtles.domain.model.Song +import com.squirtles.musicroad.favorite.navigation.navigateFavorite +import com.squirtles.musicroad.map.navigation.navigateMap +import com.squirtles.musicroad.map.navigation.navigatePickDetail +import com.squirtles.musicroad.navigation.Route +import com.squirtles.musicroad.search.navigation.navigateCreate +import com.squirtles.musicroad.search.navigation.navigateSearch +import com.squirtles.musicroad.userinfo.navigation.navigateEditNotificationSetting +import com.squirtles.musicroad.userinfo.navigation.navigateEditProfile +import com.squirtles.musicroad.userinfo.navigation.navigateMyPicks +import com.squirtles.musicroad.userinfo.navigation.navigateUserInfo + +internal class MainNavigator( + val navController: NavHostController +) { + private val currentDestination: NavDestination? + @Composable get() = navController + .currentBackStackEntryAsState().value?.destination + + val startDestination = Route.Map + + fun navigateMap() { + navController.navigateMap( + navOptions { + popUpTo(startDestination) { + inclusive = true + } + launchSingleTop = true + } + ) + } + + fun navigateFavorite(userId: String) { + navController.navigateFavorite( + userId = userId, + navOptions { + launchSingleTop = true + } + ) + } + + fun navigatePickDetail(pickId: String, navigateToMap: Boolean = false) { + navController.navigatePickDetail( + pickId = pickId, + navOptions = navOptions { + if (navigateToMap) { + popUpTo(startDestination) { + inclusive = false + } + } + launchSingleTop = true + } + ) + } + + fun navigateMyPicks(userId: String) { + navController.navigateMyPicks( + userId = userId, + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateUserInfo(userId: String) { + navController.navigateUserInfo( + userId = userId, + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateEditProfile() { + navController.navigateEditProfile( + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateEditNotificationSetting() { + navController.navigateEditNotificationSetting( + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateSearch() { + navController.navigateSearch( + navOptions = navOptions { launchSingleTop = true } + ) + } + + fun navigateCreate(song: Song) { + navController.navigateCreate( + song = song, + navOptions = navOptions { launchSingleTop = true } + ) + } + + private fun popBackStack() { + navController.popBackStack() + } + + fun popBackStackIfNotMap() { + if (!isSameCurrentDestination()) { + popBackStack() + } + } + + private inline fun isSameCurrentDestination(): Boolean { + return navController.currentDestination?.hasRoute() == true + } + +} + +@Composable +internal fun rememberMainNavigator( + navController: NavHostController = rememberNavController(), +): MainNavigator = remember(navController) { + MainNavigator(navController) +} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigations/Destinations.kt b/app/src/main/java/com/squirtles/musicroad/main/navigations/Destinations.kt deleted file mode 100644 index 6f368ef2..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/navigations/Destinations.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.squirtles.musicroad.main.navigations - -object MainDestinations { - const val MAIN_ROUTE = "main" -} - -object CreatePickDestinations { - const val CREATE_ROUTE = "create" - const val SEARCH_MUSIC_ROUTE = "search_music" - const val CREATE_PICK_ROUTE = "create_pick" -} - -object ProfileDestination { - private const val PROFILE_ROUTE = "profile" - - private const val FAVORITE_PICKS_ROUTE = "favorite_picks" - private const val MY_PICKS_ROUTE = "my_picks" - - const val SETTING_PROFILE_ROUTE = "setting/profile" - const val SETTING_NOTIFICATION_ROUTE = "setting/notification" - - fun profile(userId: String?) = "$PROFILE_ROUTE/$userId" - fun favoritePicks(userId: String) = "$FAVORITE_PICKS_ROUTE/$userId" - fun myPicks(userId: String) = "$MY_PICKS_ROUTE/$userId" -} - -object PickInfoDestinations { - private const val PICK_DETAIL_ROUTE = "pick_detail" - - fun pickDetail(pickId: String) = "$PICK_DETAIL_ROUTE/$pickId" -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavGraph.kt b/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavGraph.kt deleted file mode 100644 index 5b5258e4..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavGraph.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.squirtles.musicroad.main.navigations - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavHostController -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.navigation -import androidx.navigation.navArgument -import com.squirtles.musicroad.create.CreatePickScreen -import com.squirtles.musicroad.create.CreatePickViewModel -import com.squirtles.musicroad.create.SearchMusicScreen -import com.squirtles.musicroad.map.MapScreen -import com.squirtles.musicroad.map.MapViewModel -import com.squirtles.musicroad.media.PlayerServiceViewModel -import com.squirtles.musicroad.pick.DetailPickScreen -import com.squirtles.musicroad.picklist.PickListScreen -import com.squirtles.musicroad.picklist.PickListType -import com.squirtles.musicroad.profile.ProfileScreen -import com.squirtles.musicroad.setting.SettingNotificationScreen -import com.squirtles.musicroad.setting.SettingProfileScreen - -@Composable -fun MainNavGraph( - modifier: Modifier = Modifier, - mapViewModel: MapViewModel = hiltViewModel(), - playerServiceViewModel: PlayerServiceViewModel = hiltViewModel(), - navController: NavHostController, - navigationActions: MainNavigationActions, -) { - NavHost( - navController = navController, - startDestination = MainDestinations.MAIN_ROUTE, - modifier = modifier - ) { - composable(MainDestinations.MAIN_ROUTE) { - MapScreen( - mapViewModel = mapViewModel, - playerServiceViewModel = playerServiceViewModel, - onFavoriteClick = { userId -> navigationActions.navigateToFavoritePicks(userId) }, - onCenterClick = navigationActions.navigateToSearch, - onUserInfoClick = { userId -> navigationActions.navigateToProfile(userId) }, - onPickSummaryClick = { pickId -> navigationActions.navigateToPickDetail(pickId) }, - ) - } - - composable( - route = ProfileDestination.favoritePicks("{userId}"), - arguments = listOf(navArgument("userId") { type = NavType.StringType }) - ) { backStackEntry -> - val userId = backStackEntry.arguments?.getString("userId") ?: "" - - PickListScreen( - userId = userId, - pickListType = PickListType.FAVORITE, - onBackClick = { navController.navigateUp() }, - onItemClick = { pickId -> navigationActions.navigateToPickDetail(pickId) } - ) - } - - composable( - route = ProfileDestination.myPicks("{userId}"), - arguments = listOf(navArgument("userId") { type = NavType.StringType }) - ) { backStackEntry -> - val userId = backStackEntry.arguments?.getString("userId") ?: "" - - PickListScreen( - userId = userId, - pickListType = PickListType.CREATED, - onBackClick = { navController.navigateUp() }, - onItemClick = { pickId -> navigationActions.navigateToPickDetail(pickId) } - ) - } - - composable( - route = ProfileDestination.profile("{userId}"), - arguments = listOf(navArgument("userId") { - type = NavType.StringType - nullable = true - defaultValue = null - }) - ) { backStackEntry -> - val userId = backStackEntry.arguments?.getString("userId") - ProfileScreen( - userId = userId, - onBackClick = { navController.navigateUp() }, - onBackToMapClick = navigationActions.navigateToMain, - onFavoritePicksClick = { navigationActions.navigateToFavoritePicks(it) }, - onMyPicksClick = { navigationActions.navigateToMyPicks(it) }, - onSettingProfileClick = { navController.navigate(ProfileDestination.SETTING_PROFILE_ROUTE) }, - onSettingNotificationClick = { navController.navigate(ProfileDestination.SETTING_NOTIFICATION_ROUTE) } - ) - } - - composable(ProfileDestination.SETTING_PROFILE_ROUTE) { - SettingProfileScreen(onBackClick = { navController.navigateUp() }) - } - - composable(ProfileDestination.SETTING_NOTIFICATION_ROUTE) { - SettingNotificationScreen(onBackClick = { navController.navigateUp() }) - } - - navigation( - startDestination = CreatePickDestinations.SEARCH_MUSIC_ROUTE, - route = CreatePickDestinations.CREATE_ROUTE - ) { - composable(CreatePickDestinations.SEARCH_MUSIC_ROUTE) { - val parentEntry = remember(it) { - navController.getBackStackEntry(CreatePickDestinations.CREATE_ROUTE) - } - SearchMusicScreen( - createPickViewModel = hiltViewModel(parentEntry), - onBackClick = { navController.navigateUp() }, - onItemClick = navigationActions.navigateToCreate - ) - } - - composable(CreatePickDestinations.CREATE_PICK_ROUTE) { - val parentEntry = remember(it) { - navController.getBackStackEntry(CreatePickDestinations.CREATE_ROUTE) - } - CreatePickScreen( - createPickViewModel = hiltViewModel(parentEntry), - onBackClick = { navController.navigateUp() }, - onCreateClick = { pickId -> navigationActions.navigateToPickDetail(pickId) } - ) - } - } - - composable( - route = PickInfoDestinations.pickDetail("{pickId}"), - arguments = listOf(navArgument("pickId") { type = NavType.StringType }) - ) { backStackEntry -> - val pickId = backStackEntry.arguments?.getString("pickId") ?: "" - - DetailPickScreen( - pickId = pickId, - playerServiceViewModel = playerServiceViewModel, - onProfileClick = { userId -> navigationActions.navigateToProfile(userId) }, - onBackClick = { // 픽 등록에서 정보 화면으로 간 것이라면 뒤로 가기 시 메인으로, 아니라면 이전 화면으로 - if (navController.previousBackStackEntry?.destination?.route == CreatePickDestinations.CREATE_PICK_ROUTE) { - navigationActions.navigateToMain() - } else { - navController.navigateUp() - } - }, - onDeleted = { mapViewModel.resetClickedMarkerState(it) }, - ) - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavigationActions.kt b/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavigationActions.kt deleted file mode 100644 index 95b888a4..00000000 --- a/app/src/main/java/com/squirtles/musicroad/main/navigations/MainNavigationActions.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.squirtles.musicroad.main.navigations - -import androidx.navigation.NavHostController - -class MainNavigationActions(navController: NavHostController) { - val navigateToMain: () -> Unit = { - navController.navigate(MainDestinations.MAIN_ROUTE) { - popUpTo(route = MainDestinations.MAIN_ROUTE) { - inclusive = true - } - launchSingleTop = true - } - } - - val navigateToFavoritePicks: (String) -> Unit = { userId -> - navController.navigate(ProfileDestination.favoritePicks(userId)) { - launchSingleTop = true - } - } - - val navigateToMyPicks: (String) -> Unit = { userId -> - navController.navigate(ProfileDestination.myPicks(userId)) { - launchSingleTop = true - } - } - - val navigateToProfile: (String?) -> Unit = { userId -> - navController.navigate(ProfileDestination.profile(userId)) { - launchSingleTop = true - } - } - - val navigateToSearch: () -> Unit = { - navController.navigate(CreatePickDestinations.SEARCH_MUSIC_ROUTE) { - launchSingleTop = true - } - } - - val navigateToCreate: () -> Unit = { - navController.navigate(CreatePickDestinations.CREATE_PICK_ROUTE) { - launchSingleTop = true - } - } - - val navigateToPickDetail: (String) -> Unit = { pickId -> - navController.navigate(PickInfoDestinations.pickDetail(pickId)) { - launchSingleTop = true - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/map/Constants.kt b/app/src/main/java/com/squirtles/musicroad/map/Constants.kt new file mode 100644 index 00000000..92fad232 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/map/Constants.kt @@ -0,0 +1,16 @@ +package com.squirtles.musicroad.map + +internal enum class BottomNavigationSize( + val size: Int +) { + WIDTH(245), + HEIGHT(50), + HORIZONTAL_PADDING(32) +} + +internal enum class BottomNavigationIconSize( + val size: Int, +) { + CENTER(82), + CENTER_ICON(34) +} diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt index 11011288..919a2313 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapScreen.kt @@ -31,10 +31,10 @@ import com.squirtles.musicroad.account.GoogleId import com.squirtles.musicroad.common.SignInAlertDialog import com.squirtles.musicroad.common.VerticalSpacer import com.squirtles.musicroad.main.MainActivity -import com.squirtles.musicroad.map.components.BottomNavigation import com.squirtles.musicroad.map.components.ClusterBottomSheet import com.squirtles.musicroad.map.components.InfoWindow import com.squirtles.musicroad.map.components.LoadingDialog +import com.squirtles.musicroad.map.components.MapBottomNavBar import com.squirtles.musicroad.map.components.PickNotificationBanner import com.squirtles.musicroad.media.PlayerServiceViewModel @@ -44,7 +44,7 @@ fun MapScreen( playerServiceViewModel: PlayerServiceViewModel, onFavoriteClick: (String) -> Unit, onCenterClick: () -> Unit, - onUserInfoClick: (String?) -> Unit, + onUserInfoClick: (String) -> Unit, onPickSummaryClick: (String) -> Unit, accountViewModel: AccountViewModel = hiltViewModel() ) { @@ -134,7 +134,7 @@ fun MapScreen( VerticalSpacer(16) - BottomNavigation( + MapBottomNavBar( modifier = Modifier.padding(bottom = 16.dp), lastLocation = lastLocation, onFavoriteClick = { @@ -155,7 +155,12 @@ fun MapScreen( } }, onUserInfoClick = { - onUserInfoClick(mapViewModel.getUserId()) + mapViewModel.getUserId()?.let { + onUserInfoClick(it) + } ?: run { + signInDialogDescription = getString(context, R.string.sign_in_dialog) + showSignInDialog = true + } } ) } diff --git a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt index 46d600b2..c95f1a60 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/MapViewModel.kt @@ -10,10 +10,10 @@ import com.naver.maps.map.CameraPosition import com.naver.maps.map.clustering.Clusterer import com.naver.maps.map.overlay.Marker import com.squirtles.domain.model.Pick -import com.squirtles.domain.usecase.local.FetchLastLocationUseCase -import com.squirtles.domain.usecase.local.GetCurrentUserUseCase -import com.squirtles.domain.usecase.local.SaveLastLocationUseCase -import com.squirtles.domain.usecase.pick.FetchPickInAreaUseCase +import com.squirtles.domain.usecase.location.GetLastLocationUseCase +import com.squirtles.domain.usecase.location.SaveLastLocationUseCase +import com.squirtles.domain.usecase.pick.FetchPickUseCase +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase import com.squirtles.musicroad.map.marker.MarkerKey import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -30,9 +30,9 @@ data class MarkerState( @HiltViewModel class MapViewModel @Inject constructor( - fetchLastLocationUseCase: FetchLastLocationUseCase, + getLastLocationUseCase: GetLastLocationUseCase, private val saveLastLocationUseCase: SaveLastLocationUseCase, - private val fetchPickInAreaUseCase: FetchPickInAreaUseCase, + private val fetchPickUseCase: FetchPickUseCase, private val getCurrentUserUseCase: GetCurrentUserUseCase ) : ViewModel() { @@ -57,7 +57,7 @@ class MapViewModel @Inject constructor( // LocalDataSource에 저장되는 위치 정보 // Firestore 데이터 쿼리 작업 최소화 및 위치데이터 공유 용도 - val lastLocation: StateFlow = fetchLastLocationUseCase() + val lastLocation: StateFlow = getLastLocationUseCase() fun getUserId() = getCurrentUserUseCase()?.userId @@ -142,7 +142,7 @@ class MapViewModel @Inject constructor( viewModelScope.launch { _centerLatLng.value?.run { val radiusInM = leftTop.distanceTo(this) - fetchPickInAreaUseCase(this.latitude, this.longitude, radiusInM) + fetchPickUseCase(this.latitude, this.longitude, radiusInM) .onSuccess { pickList -> val newKeyTagMap: MutableMap = mutableMapOf() pickList.forEach { pick -> @@ -170,7 +170,7 @@ class MapViewModel @Inject constructor( fun requestPickNotificationArea(location: Location, notiRadius: Double) { viewModelScope.launch { - fetchPickInAreaUseCase(location.latitude, location.longitude, notiRadius) + fetchPickUseCase(location.latitude, location.longitude, notiRadius) .onSuccess { _nearPicks.emit(it) }.onFailure { diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt index 65dbbdde..ff2e5770 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/ClusterBottomSheet.kt @@ -109,7 +109,7 @@ fun BottomSheetItem( verticalAlignment = Alignment.CenterVertically ) { AlbumImage( - imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT), + imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), modifier = Modifier .size(45.dp) .clip(RoundedCornerShape(4.dp)) diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt index dc3564c5..b144d313 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/InfoWindowCard.kt @@ -58,7 +58,7 @@ fun InfoWindow( horizontalArrangement = Arrangement.spacedBy(16.dp) ) { AlbumImage( - imageUrl = pick.song.getImageUrlWithSize(RequestImageSize), + imageUrl = pick.song.getImageUrlWithSize(RequestImageSize.width, RequestImageSize.height), modifier = Modifier .size(90.dp) .clip(RoundedCornerShape(4.dp)) diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/BottomNavigation.kt b/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt similarity index 56% rename from app/src/main/java/com/squirtles/musicroad/map/components/BottomNavigation.kt rename to app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt index 6cc689f6..e50daaab 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/BottomNavigation.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/MapBottomNavBar.kt @@ -10,9 +10,6 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.FavoriteBorder -import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -20,20 +17,26 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.musicroad.R +import com.squirtles.musicroad.map.BottomNavigationIconSize +import com.squirtles.musicroad.map.BottomNavigationSize +import com.squirtles.musicroad.map.navigation.NavTab import com.squirtles.musicroad.ui.theme.MusicRoadTheme +import com.squirtles.musicroad.ui.theme.Primary @Composable -fun BottomNavigation( +internal fun MapBottomNavBar( modifier: Modifier = Modifier, lastLocation: Location?, onFavoriteClick: () -> Unit, onCenterClick: () -> Unit, - onUserInfoClick: () -> Unit + onUserInfoClick: () -> Unit, ) { Box( modifier = modifier, @@ -41,65 +44,72 @@ fun BottomNavigation( ) { Row( modifier = Modifier - .size(245.dp, 50.dp) + .size(BottomNavigationSize.WIDTH.size.dp, BottomNavigationSize.HEIGHT.size.dp) .clip(CircleShape) .background(color = MaterialTheme.colorScheme.surface) ) { // 왼쪽 버튼 - Box( + MapBottomNavigationItem( modifier = Modifier .weight(1f) .fillMaxHeight() - .clickable { onFavoriteClick() }, - contentAlignment = Alignment.CenterStart - ) { - Icon( - imageVector = Icons.Default.FavoriteBorder, - contentDescription = stringResource(R.string.map_navigation_favorite_icon_description), - modifier = Modifier.padding(start = BottomNavigationHorizontalPadding), - tint = MaterialTheme.colorScheme.primary - ) - } + .padding(end = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), + tab = NavTab.FAVORITE, + painter = null, + tint = Primary, + onClick = onFavoriteClick + ) // 오른쪽 버튼 - Box( + MapBottomNavigationItem( modifier = Modifier .weight(1f) .fillMaxHeight() - .clickable { onUserInfoClick() }, - contentAlignment = Alignment.CenterEnd - ) { - Icon( - imageVector = Icons.Outlined.AccountCircle, - contentDescription = stringResource(R.string.map_navigation_setting_icon_description), - modifier = Modifier.padding(end = BottomNavigationHorizontalPadding), - tint = MaterialTheme.colorScheme.primary - ) - } + .padding(start = BottomNavigationSize.HORIZONTAL_PADDING.size.dp), + tab = NavTab.MYPAGE, + painter = null, + tint = Primary, + onClick = onUserInfoClick + ) } // 중앙 버튼 - Box( + MapBottomNavigationItem( modifier = Modifier - .size(82.dp) + .size(BottomNavigationIconSize.CENTER.size.dp) .clip(CircleShape) .background( color = lastLocation?.let { MaterialTheme.colorScheme.primary } ?: Color.Gray - ) - .clickable(enabled = lastLocation != null) { - onCenterClick() - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(R.drawable.ic_musical_note_64), - contentDescription = stringResource(R.string.map_navigation_center_icon_description), - modifier = Modifier.size(34.dp), - tint = MaterialTheme.colorScheme.onPrimary - ) - } + ), + tab = NavTab.SEARCH, + painter = painterResource(R.drawable.ic_musical_note_64), + tint = MaterialTheme.colorScheme.onPrimary, + onClick = onCenterClick + ) + } +} + +@Composable +private fun MapBottomNavigationItem( + modifier: Modifier = Modifier, + tab: NavTab, + painter: Painter?, + tint: Color, + onClick: () -> Unit +) { + Box( + modifier = modifier + .clickable { onClick() }, + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = if (tab.iconSize != null) Modifier.size(tab.iconSize.dp) else Modifier, + painter = painter ?: rememberVectorPainter(tab.icon), + contentDescription = stringResource(tab.contentDescription), + tint = tint + ) } } @@ -107,7 +117,7 @@ fun BottomNavigation( @Composable fun BottomNavigationLightPreview() { MusicRoadTheme { - BottomNavigation( + MapBottomNavBar( onFavoriteClick = {}, lastLocation = null, onCenterClick = {}, @@ -120,7 +130,7 @@ fun BottomNavigationLightPreview() { @Composable fun BottomNavigationDarkPreview() { MusicRoadTheme { - BottomNavigation( + MapBottomNavBar( onFavoriteClick = {}, lastLocation = null, onCenterClick = {}, @@ -128,5 +138,3 @@ fun BottomNavigationDarkPreview() { ) } } - -private val BottomNavigationHorizontalPadding = 32.dp diff --git a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt index b8b8d38a..ed665a9d 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/components/PickNotificationBanner.kt @@ -12,9 +12,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -27,7 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squirtles.domain.model.Pick import com.squirtles.musicroad.R -import com.squirtles.musicroad.pick.PickViewModel.Companion.DEFAULT_PICK +import com.squirtles.musicroad.detail.DetailViewModel.Companion.DEFAULT_PICK import com.squirtles.musicroad.ui.theme.Black import com.squirtles.musicroad.ui.theme.MusicRoadTheme import com.squirtles.musicroad.ui.theme.White diff --git a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt b/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt index 4d17346d..159f19a0 100644 --- a/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt +++ b/app/src/main/java/com/squirtles/musicroad/map/marker/LeafMarkerIconView.kt @@ -72,7 +72,7 @@ class LeafMarkerIconView( fun setLeafMarkerIcon(pick: Pick, onImageLoaded: () -> Unit) { val song = pick.song - loadImage(song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT)) { + loadImage(song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height)) { onImageLoaded() } } diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt new file mode 100644 index 00000000..507103d6 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/map/navigation/MapNavigation.kt @@ -0,0 +1,56 @@ +package com.squirtles.musicroad.map.navigation + +import android.content.Context +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.musicroad.map.MapScreen +import com.squirtles.musicroad.map.MapViewModel +import com.squirtles.musicroad.media.PlayerServiceViewModel +import com.squirtles.musicroad.navigation.MapRoute +import com.squirtles.musicroad.navigation.Route +import com.squirtles.musicroad.detail.PickDetailScreen + +fun NavController.navigateMap(navOptions: NavOptions? = null) { + navigate(Route.Map, navOptions) +} + +fun NavController.navigatePickDetail(pickId: String, navOptions: NavOptions? = null) { + navigate(MapRoute.PickDetail(pickId), navOptions) +} + +fun NavGraphBuilder.mapNavGraph( + mapViewModel: MapViewModel, + playerServiceViewModel: PlayerServiceViewModel, + onFavoriteClick: (String) -> Unit, + onCenterClick: () -> Unit, + onUserInfoClick: (String) -> Unit, + onPickSummaryClick: (String) -> Unit, + onBackClick: () -> Unit, + onDeleted: (Context) -> Unit, +) { + composable { + MapScreen( + mapViewModel = mapViewModel, + playerServiceViewModel = playerServiceViewModel, + onFavoriteClick = onFavoriteClick, + onCenterClick = onCenterClick, + onUserInfoClick = onUserInfoClick, + onPickSummaryClick = onPickSummaryClick, + ) + } + + composable { backStackEntry -> + val pickId = backStackEntry.toRoute().pickId + + PickDetailScreen( + pickId = pickId, + playerServiceViewModel = playerServiceViewModel, + onUserInfoClick = onUserInfoClick, + onBackClick = onBackClick, + onDeleted = onDeleted, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt b/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt new file mode 100644 index 00000000..d24b0c21 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/map/navigation/NavTab.kt @@ -0,0 +1,34 @@ +package com.squirtles.musicroad.map.navigation + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material.icons.outlined.AccountCircle +import androidx.compose.material.icons.outlined.MusicNote +import androidx.compose.ui.graphics.vector.ImageVector +import com.squirtles.musicroad.R +import com.squirtles.musicroad.map.BottomNavigationIconSize + +internal enum class NavTab( + @StringRes val contentDescription: Int, + val icon: ImageVector, + val iconSize: Int?, +) { + FAVORITE( + contentDescription = R.string.map_navigation_favorite_icon_description, + icon = Icons.Default.FavoriteBorder, + iconSize = null, + ), + + MYPAGE( + contentDescription = R.string.map_navigation_setting_icon_description, + icon = Icons.Outlined.AccountCircle, + iconSize = null, + ), + + SEARCH( + contentDescription = R.string.map_navigation_center_icon_description, + icon = Icons.Outlined.MusicNote, + iconSize = BottomNavigationIconSize.CENTER_ICON.size, + ), +} diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt new file mode 100644 index 00000000..ee627663 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickListViewModel.kt @@ -0,0 +1,25 @@ +package com.squirtles.musicroad.mypick + +import com.squirtles.domain.usecase.mypick.DeletePickUseCase +import com.squirtles.domain.usecase.mypick.FetchMyPicksUseCase +import com.squirtles.domain.usecase.order.GetMyPickListOrderUseCase +import com.squirtles.domain.usecase.order.SaveMyPickListOrderUseCase +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.musicroad.common.picklist.PickListViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class MyPickListViewModel @Inject constructor( + fetchMyPicksUseCase: FetchMyPicksUseCase, + getMyPickListOrderUseCase: GetMyPickListOrderUseCase, + saveMyPickListOrderUseCase: SaveMyPickListOrderUseCase, + deletePickUseCase: DeletePickUseCase, + getCurrentUserUseCase: GetCurrentUserUseCase +) : PickListViewModel( + fetchPickListUseCase = fetchMyPicksUseCase, + getPickListOrderUseCase = getMyPickListOrderUseCase, + savePickListOrderUseCase = saveMyPickListOrderUseCase, + removePickUseCase = deletePickUseCase, + getCurrentUserUseCase = getCurrentUserUseCase +) diff --git a/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt new file mode 100644 index 00000000..bfd69357 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/mypick/MyPickScreen.kt @@ -0,0 +1,45 @@ +package com.squirtles.musicroad.mypick + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.squirtles.musicroad.common.picklist.PickListType +import com.squirtles.musicroad.picklist.PickListScreenContents + +@Composable +fun MyPickScreen( + userId: String, + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + myPickListViewModel: MyPickListViewModel = hiltViewModel() +) { + val uiState by myPickListViewModel.pickListUiState.collectAsStateWithLifecycle() + val selectedPicksId by myPickListViewModel.selectedPicksId.collectAsStateWithLifecycle() + var showOrderBottomSheet by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(Unit) { + myPickListViewModel.fetchPickList(userId) + } + + PickListScreenContents( + userId = userId, + showOrderBottomSheet = showOrderBottomSheet, + selectedPicksId = selectedPicksId, + pickListType = PickListType.CREATED, + uiState = uiState, + onBackClick = onBackClick, + onItemClick = onItemClick, + setListOrder = myPickListViewModel::setListOrder, + setOrderBottomSheetVisibility = { showOrderBottomSheet = it }, + selectAllPicks = myPickListViewModel::selectAllPicks, + deselectAllPicks = myPickListViewModel::deselectAllPicks, + toggleSelectedPick = myPickListViewModel::toggleSelectedPick, + deleteSelectedPicks = myPickListViewModel::deleteSelectedPicks, + getUserId = { myPickListViewModel.getUserId().toString() }, + ) +} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt new file mode 100644 index 00000000..e5c948a1 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/navigation/MainRoute.kt @@ -0,0 +1,15 @@ +package com.squirtles.musicroad.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MainRoute : Route { + @Serializable + data object Search : MainRoute + + @Serializable + data class Favorite(val userId: String) : MainRoute + + @Serializable + data class UserInfo(val userId: String) : MainRoute +} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt new file mode 100644 index 00000000..4a38424e --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/navigation/MapRoute.kt @@ -0,0 +1,9 @@ +package com.squirtles.musicroad.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MapRoute : Route { + @Serializable + data class PickDetail(val pickId: String) : MapRoute +} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt b/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt new file mode 100644 index 00000000..7e7db78e --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/navigation/Route.kt @@ -0,0 +1,9 @@ +package com.squirtles.musicroad.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Route { + @Serializable + data object Map : Route +} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt new file mode 100644 index 00000000..1765ecea --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/navigation/SearchRoute.kt @@ -0,0 +1,21 @@ +package com.squirtles.musicroad.navigation + +import androidx.lifecycle.SavedStateHandle +import androidx.navigation.toRoute +import com.squirtles.domain.model.Song +import com.squirtles.musicroad.utils.serializableType +import kotlinx.serialization.Serializable +import kotlin.reflect.typeOf + +@Serializable +sealed interface SearchRoute : Route { + @Serializable + data class Create(val song: Song) : SearchRoute { + companion object { + val typeMap = mapOf(typeOf() to serializableType()) + + fun from(savedStateHandle: SavedStateHandle) = + savedStateHandle.toRoute(typeMap) + } + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt b/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt new file mode 100644 index 00000000..e455b807 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/navigation/UserInfoRoute.kt @@ -0,0 +1,16 @@ +package com.squirtles.musicroad.navigation + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface UserInfoRoute : Route { + @Serializable + data class MyPicks(val userId: String) : UserInfoRoute + + @Serializable + data object EditProfile : UserInfoRoute + + @Serializable + data object EditNotification : UserInfoRoute +} + diff --git a/app/src/main/java/com/squirtles/musicroad/pick/DetailPickUiState.kt b/app/src/main/java/com/squirtles/musicroad/pick/DetailPickUiState.kt deleted file mode 100644 index 304686b8..00000000 --- a/app/src/main/java/com/squirtles/musicroad/pick/DetailPickUiState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.musicroad.pick - -import com.squirtles.domain.model.Pick - -sealed class DetailPickUiState { - data object Loading : DetailPickUiState() - data class Success(val pick: Pick, val isFavorite: Boolean) : DetailPickUiState() - data object Deleted : DetailPickUiState() - data object Error : DetailPickUiState() -} diff --git a/app/src/main/java/com/squirtles/musicroad/picklist/PickListViewModel.kt b/app/src/main/java/com/squirtles/musicroad/picklist/PickListViewModel.kt deleted file mode 100644 index f64b1140..00000000 --- a/app/src/main/java/com/squirtles/musicroad/picklist/PickListViewModel.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.squirtles.musicroad.picklist - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.squirtles.domain.model.Order -import com.squirtles.domain.model.Pick -import com.squirtles.domain.usecase.favoritepick.DeleteFavoriteUseCase -import com.squirtles.domain.usecase.favoritepick.FetchFavoritePicksUseCase -import com.squirtles.domain.usecase.local.GetCurrentUserUseCase -import com.squirtles.domain.usecase.local.GetFavoriteListOrderUseCase -import com.squirtles.domain.usecase.local.GetMyListOrderUseCase -import com.squirtles.domain.usecase.local.SaveFavoriteListOrderUseCase -import com.squirtles.domain.usecase.local.SaveMyListOrderUseCase -import com.squirtles.domain.usecase.mypick.DeletePickUseCase -import com.squirtles.domain.usecase.mypick.FetchMyPicksUseCase -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class PickListViewModel @Inject constructor( - private val fetchFavoritePicksUseCase: FetchFavoritePicksUseCase, - private val fetchMyPicksUseCase: FetchMyPicksUseCase, - private val getCurrentUserUseCase: GetCurrentUserUseCase, - private val getFavoriteListOrderUseCase: GetFavoriteListOrderUseCase, - private val getMyListOrderUseCase: GetMyListOrderUseCase, - private val saveFavoriteListOrderUseCase: SaveFavoriteListOrderUseCase, - private val saveMyListOrderUseCase: SaveMyListOrderUseCase, - private val deleteFavoriteUseCase: DeleteFavoriteUseCase, - private val deletePickUseCase: DeletePickUseCase, -) : ViewModel() { - - private var defaultList: List? = null - - private val _pickListUiState = MutableStateFlow(PickListUiState.Loading) - val pickListUiState = _pickListUiState.asStateFlow() - - private val _selectedPicksId = MutableStateFlow>(emptySet()) - val selectedPicksId = _selectedPicksId.asStateFlow() - - fun fetchFavoritePicks(userId: String) { - viewModelScope.launch { - fetchFavoritePicksUseCase(userId) - .onSuccess { favoritePicks -> - defaultList = favoritePicks - setList(getFavoriteListOrderUseCase()) - } - .onFailure { - _pickListUiState.emit(PickListUiState.Error) - } - } - } - - fun fetchMyPicks(userId: String) { - viewModelScope.launch { - fetchMyPicksUseCase(userId) - .onSuccess { myPicks -> - defaultList = myPicks - setList(getMyListOrderUseCase()) - } - .onFailure { - _pickListUiState.emit(PickListUiState.Error) - } - } - } - - fun setListOrder(type: PickListType, order: Order) { - viewModelScope.launch { - when (type) { - PickListType.FAVORITE -> saveFavoriteListOrderUseCase(order) - PickListType.CREATED -> saveMyListOrderUseCase(order) - } - setList(order) - } - } - - fun toggleSelectedPick(pickId: String) { - val curSelectedPicksId = _selectedPicksId.value - _selectedPicksId.value = - if (curSelectedPicksId.contains(pickId)) curSelectedPicksId - pickId else curSelectedPicksId + pickId - } - - fun selectAllPicks() { - defaultList?.let { pickList -> - _selectedPicksId.value = pickList.map { it.id }.toSet() - } - } - - fun deselectAllPicks() { - _selectedPicksId.value = emptySet() - } - - fun deleteSelectedPicks(type: PickListType) { - viewModelScope.launch { - getUserId()?.let { userId -> - _pickListUiState.value = PickListUiState.Loading - - val deleteJobList = _selectedPicksId.value.map { pickId -> - when (type) { - PickListType.FAVORITE -> async { deleteFavoriteUseCase(pickId, userId) } - PickListType.CREATED -> async { deletePickUseCase(pickId, userId) } - } - } - val deleteJobResults = deleteJobList.awaitAll() - - deselectAllPicks() - if (deleteJobResults.all { it.isSuccess }) { - when (type) { - PickListType.FAVORITE -> fetchFavoritePicks(userId) - PickListType.CREATED -> fetchMyPicks(userId) - } - } else { - _pickListUiState.value = PickListUiState.Error - Log.e("PickListViewModel", "[픽 목록] 다중 삭제 오류") - } - } - } - } - - private fun setList(order: Order) { - defaultList?.let { pickList -> - val sortedList = when (order) { - Order.LATEST -> pickList - - Order.OLDEST -> pickList.reversed() - - Order.FAVORITE_DESC -> - pickList.sortedByDescending { it.favoriteCount } - } - - _pickListUiState.value = PickListUiState.Success( - pickList = sortedList, - order = order - ) - } - } - - fun getUserId() = getCurrentUserUseCase()?.userId -} diff --git a/app/src/main/java/com/squirtles/musicroad/profile/ProfileScreen.kt b/app/src/main/java/com/squirtles/musicroad/profile/ProfileScreen.kt deleted file mode 100644 index 674c9b2b..00000000 --- a/app/src/main/java/com/squirtles/musicroad/profile/ProfileScreen.kt +++ /dev/null @@ -1,223 +0,0 @@ -package com.squirtles.musicroad.profile - -import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.Logout -import androidx.compose.material.icons.filled.MusicNote -import androidx.compose.material.icons.outlined.Archive -import androidx.compose.material.icons.outlined.Map -import androidx.compose.material.icons.outlined.Notifications -import androidx.compose.material.icons.outlined.SwitchAccount -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.flowWithLifecycle -import coil3.compose.AsyncImage -import coil3.request.ImageRequest -import coil3.request.crossfade -import com.squirtles.musicroad.R -import com.squirtles.musicroad.account.AccountViewModel -import com.squirtles.musicroad.account.GoogleId -import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.common.DefaultTopAppBar -import com.squirtles.musicroad.common.GoogleSignInButton -import com.squirtles.musicroad.common.HorizontalSpacer -import com.squirtles.musicroad.common.VerticalSpacer -import com.squirtles.musicroad.ui.theme.Primary -import com.squirtles.musicroad.ui.theme.White - -@Composable -fun ProfileScreen( - userId: String?, - onBackClick: () -> Unit, - onBackToMapClick: () -> Unit, - onFavoritePicksClick: (String) -> Unit, - onMyPicksClick: (String) -> Unit, - onSettingProfileClick: () -> Unit, - onSettingNotificationClick: () -> Unit, - profileViewModel: ProfileViewModel = hiltViewModel(), - accountViewModel: AccountViewModel = hiltViewModel() -) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val scrollState = rememberScrollState() - val user by profileViewModel.profileUser.collectAsStateWithLifecycle() - - val onSettingSignOutClick: () -> Unit = { - GoogleId(context).signOut() - accountViewModel.signOut() - } - - LaunchedEffect(Unit) { - userId?.let { - profileViewModel.getUserById(userId) - } - - accountViewModel.signOutSuccess - .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) - .collect { isSuccess -> - if (isSuccess) { - onBackToMapClick() - Log.d("SignOut", "로그아웃") - } - } - } - - Scaffold( - topBar = { - DefaultTopAppBar( - title = if (userId == null) stringResource(id = R.string.profile_sign_in_title) else user.userName, - onBackClick = onBackClick - ) - } - ) { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) - .padding(innerPadding), - ) { - if (userId == null) { - GoogleSignInButton( - onClick = { - GoogleId(context).signIn( - onSuccess = { credential -> - accountViewModel.signIn(credential) - onBackToMapClick() - } - ) - }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp) - .align(Alignment.TopCenter) - ) - } else { - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .padding(bottom = 96.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - VerticalSpacer(16) - - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(user.userProfileImage) - .crossfade(true) - .build(), - contentDescription = stringResource(R.string.user_info_profile_image), - modifier = Modifier - .size(180.dp) - .clip(CircleShape), - placeholder = painterResource(R.drawable.img_user_default_profile), - error = painterResource(R.drawable.img_user_default_profile), - contentScale = ContentScale.Crop, - ) - - VerticalSpacer(40) - - ProfileMenus( - title = stringResource(R.string.user_info_pick_category_title), - menus = listOf( - MenuItem( - imageVector = Icons.Outlined.Archive, - contentDescription = stringResource(R.string.user_info_favorite_menu_icon_description), - menuTitle = stringResource(R.string.user_info_favorite_menu_title), - onMenuClick = { onFavoritePicksClick(userId) } - ), - MenuItem( - imageVector = Icons.Default.MusicNote, - contentDescription = stringResource(R.string.user_info_created_by_self_menu_icon_description), - menuTitle = stringResource(R.string.user_info_created_by_self_menu_title), - onMenuClick = { onMyPicksClick(userId) } - ) - ) - ) - - if (userId == profileViewModel.currentUser?.userId) { - ProfileMenus( - title = stringResource(R.string.user_info_setting_category_title), - menus = listOf( - MenuItem( - imageVector = Icons.Outlined.SwitchAccount, - contentDescription = stringResource(R.string.user_info_setting_profile_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_profile_menu_title), - onMenuClick = onSettingProfileClick - ), - MenuItem( - imageVector = Icons.Outlined.Notifications, - contentDescription = stringResource(R.string.user_info_setting_notification_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_notification_menu_title), - onMenuClick = onSettingNotificationClick - ), - MenuItem( - imageVector = Icons.AutoMirrored.Outlined.Logout, - contentDescription = stringResource(R.string.user_info_setting_sign_out_menu_icon_description), - menuTitle = stringResource(R.string.user_info_setting_sign_out_menu_title), - onMenuClick = onSettingSignOutClick - ) - ) - ) - } - } - - ExtendedFloatingActionButton( - onClick = onBackToMapClick, - modifier = Modifier - .wrapContentWidth() - .padding(horizontal = 8.dp) - .padding(bottom = 48.dp) - .align(Alignment.BottomCenter), - shape = CircleShape, - containerColor = Primary, - contentColor = White, - ) { - Icon( - imageVector = Icons.Outlined.Map, - contentDescription = stringResource(R.string.user_info_icon_map_description), - tint = White - ) - - HorizontalSpacer(8) - - Text( - text = stringResource(R.string.user_info_back_to_map_button_text), - color = White, - style = MaterialTheme.typography.bodyLarge - ) - } - } - } - } -} diff --git a/app/src/main/java/com/squirtles/musicroad/create/SearchMusicScreen.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt similarity index 93% rename from app/src/main/java/com/squirtles/musicroad/create/SearchMusicScreen.kt rename to app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt index 076be54a..d76a66eb 100644 --- a/app/src/main/java/com/squirtles/musicroad/create/SearchMusicScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchMusicScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.create +package com.squirtles.musicroad.search import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -50,6 +50,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems @@ -61,19 +62,20 @@ import com.squirtles.musicroad.common.Constants.COLOR_STOPS import com.squirtles.musicroad.common.Constants.REQUEST_IMAGE_SIZE_DEFAULT import com.squirtles.musicroad.common.HorizontalSpacer import com.squirtles.musicroad.common.VerticalSpacer +import com.squirtles.musicroad.create.SearchUiState import com.squirtles.musicroad.ui.theme.Gray import com.squirtles.musicroad.ui.theme.White @Composable fun SearchMusicScreen( - createPickViewModel: CreatePickViewModel, - onBackClick: () -> Boolean, - onItemClick: () -> Unit, + onBackClick: () -> Unit, + onItemClick: (Song) -> Unit, + searchViewModel: SearchViewModel = hiltViewModel(), ) { val focusManager = LocalFocusManager.current - val searchText by createPickViewModel.searchText.collectAsStateWithLifecycle() - val searchUiState by createPickViewModel.searchUiState.collectAsStateWithLifecycle() - val searchResult = createPickViewModel.searchResult.collectAsLazyPagingItems() + val searchText by searchViewModel.searchText.collectAsStateWithLifecycle() + val searchUiState by searchViewModel.searchUiState.collectAsStateWithLifecycle() + val searchResult = searchViewModel.searchResult.collectAsLazyPagingItems() Scaffold( contentWindowInsets = WindowInsets.navigationBars, @@ -86,7 +88,7 @@ fun SearchMusicScreen( ) { SearchTopBar( keyword = searchText, - onValueChange = createPickViewModel::onSearchTextChange, + onValueChange = searchViewModel::onSearchTextChange, onBackClick = onBackClick, focusManager = focusManager ) @@ -122,9 +124,8 @@ fun SearchMusicScreen( SearchResult( searchResult = searchResult, onItemClick = { song -> - createPickViewModel.onSongItemClick(song) focusManager.clearFocus() - onItemClick() + onItemClick(song) } ) } @@ -137,7 +138,7 @@ fun SearchMusicScreen( private fun SearchTopBar( keyword: String, onValueChange: (String) -> Unit, - onBackClick: () -> Boolean, + onBackClick: () -> Unit, focusManager: FocusManager ) { @@ -260,7 +261,7 @@ private fun SongItem( verticalAlignment = Alignment.CenterVertically ) { AlbumImage( - imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT), + imageUrl = song.getImageUrlWithSize(REQUEST_IMAGE_SIZE_DEFAULT.width, REQUEST_IMAGE_SIZE_DEFAULT.height), modifier = Modifier .size(ImageSize) .clip(RoundedCornerShape(16.dp)) diff --git a/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt new file mode 100644 index 00000000..5e3a7905 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/search/SearchViewModel.kt @@ -0,0 +1,64 @@ +package com.squirtles.musicroad.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.squirtles.domain.model.Song +import com.squirtles.domain.usecase.music.FetchSongsUseCase +import com.squirtles.musicroad.create.SearchUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch +import javax.inject.Inject + +@OptIn(FlowPreview::class) +@HiltViewModel +class SearchViewModel @Inject constructor( + private val fetchSongsUseCase: FetchSongsUseCase, +) : ViewModel() { + + private val _searchUiState = MutableStateFlow(SearchUiState.HotResult) + val searchUiState = _searchUiState.asStateFlow() + + private val _searchResult = MutableStateFlow>(PagingData.empty()) + val searchResult = _searchResult.asStateFlow() + + private val _searchText = MutableStateFlow("") + val searchText = _searchText.asStateFlow() + + private var searchJob: Job? = null + + init { + viewModelScope.launch { + _searchText + .debounce(300) + .collect { searchKeyword -> + searchJob?.cancel() + if (searchKeyword.isNotBlank()) { + searchJob = launch { searchSongs(searchKeyword) } + } else { + _searchUiState.emit(SearchUiState.HotResult) + } + } + } + } + + private suspend fun searchSongs(searchKeyword: String) { + fetchSongsUseCase(searchKeyword) + .cachedIn(viewModelScope) + .collectLatest { + _searchResult.emit(it) + _searchUiState.emit(SearchUiState.SearchResult) + } + } + + fun onSearchTextChange(text: String) { + _searchText.value = text + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt new file mode 100644 index 00000000..b46e2049 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/search/navigation/SearchNavigation.kt @@ -0,0 +1,52 @@ +package com.squirtles.musicroad.search.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.domain.model.Song +import com.squirtles.musicroad.create.CreatePickScreen +import com.squirtles.musicroad.navigation.MainRoute +import com.squirtles.musicroad.navigation.SearchRoute +import com.squirtles.musicroad.search.SearchMusicScreen +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +fun NavController.navigateSearch(navOptions: NavOptions? = null) { + navigate(MainRoute.Search, navOptions) +} + +fun NavController.navigateCreate(song: Song, navOptions: NavOptions? = null) { + val encodedSong = song.copy( + previewUrl = URLEncoder.encode(song.previewUrl, StandardCharsets.UTF_8.toString()), + externalUrl = URLEncoder.encode(song.externalUrl, StandardCharsets.UTF_8.toString()), + genreNames = song.genreNames.map { URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) }, + imageUrl = URLEncoder.encode(song.imageUrl, StandardCharsets.UTF_8.toString()) + ) + navigate(SearchRoute.Create(encodedSong), navOptions) +} + +fun NavGraphBuilder.searchNavGraph( + onBackClick: () -> Unit, + onItemClick: (Song) -> Unit, + onCreateClick: (String) -> Unit +) { + composable { + SearchMusicScreen( + onBackClick = onBackClick, + onItemClick = onItemClick, // Create 이동 + ) + } + composable( + typeMap = SearchRoute.Create.typeMap + ) { backStackEntry -> + val song = backStackEntry.toRoute().song + + CreatePickScreen( + song = song, + onBackClick = onBackClick, + onCreateClick = onCreateClick, + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/profile/ProfileViewModel.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt similarity index 83% rename from app/src/main/java/com/squirtles/musicroad/profile/ProfileViewModel.kt rename to app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt index 9c79b032..d8229436 100644 --- a/app/src/main/java/com/squirtles/musicroad/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/UserInfoViewModel.kt @@ -1,12 +1,12 @@ -package com.squirtles.musicroad.profile +package com.squirtles.musicroad.userinfo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.squirtles.domain.model.User -import com.squirtles.domain.usecase.local.GetCurrentUserUseCase import com.squirtles.domain.usecase.user.FetchUserByIdUseCase import com.squirtles.domain.usecase.user.FetchUserUseCase -import com.squirtles.domain.usecase.user.UpdateUserNameUserCase +import com.squirtles.domain.usecase.user.GetCurrentUserUseCase +import com.squirtles.domain.usecase.user.UpdateUserNameUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -16,11 +16,11 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class ProfileViewModel @Inject constructor( +class UserInfoViewModel @Inject constructor( private val getCurrentUserUseCase: GetCurrentUserUseCase, private val fetchUserUseCase: FetchUserUseCase, private val fetchUserByIdUseCase: FetchUserByIdUseCase, - private val updateUserNameUserCase: UpdateUserNameUserCase + private val updateUserNameUseCase: UpdateUserNameUseCase ) : ViewModel() { private val _profileUser = MutableStateFlow(DEFAULT_USER) @@ -46,7 +46,7 @@ class ProfileViewModel @Inject constructor( viewModelScope.launch { currentUser?.let { user -> val result = runCatching { - updateUserNameUserCase(user.userId, newUserName).getOrThrow() + updateUserNameUseCase(user.userId, newUserName).getOrThrow() fetchUserUseCase(user.userId).getOrThrow() } _updateSuccess.emit(result.isSuccess) diff --git a/app/src/main/java/com/squirtles/musicroad/profile/MenuItem.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt similarity index 86% rename from app/src/main/java/com/squirtles/musicroad/profile/MenuItem.kt rename to app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt index a37617d9..ffa4050b 100644 --- a/app/src/main/java/com/squirtles/musicroad/profile/MenuItem.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/components/MenuItem.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.profile +package com.squirtles.musicroad.userinfo.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector diff --git a/app/src/main/java/com/squirtles/musicroad/profile/ProfileMenus.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt similarity index 97% rename from app/src/main/java/com/squirtles/musicroad/profile/ProfileMenus.kt rename to app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt index 3ef7a37d..c8bb9e32 100644 --- a/app/src/main/java/com/squirtles/musicroad/profile/ProfileMenus.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/components/UserInfoMenus.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.profile +package com.squirtles.musicroad.userinfo.components import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -31,7 +31,7 @@ import com.squirtles.musicroad.ui.theme.Gray import com.squirtles.musicroad.ui.theme.White @Composable -internal fun ProfileMenus( +internal fun UserInfoMenus( title: String, titleTextColor: Color = White, titleTextStyle: TextStyle = MaterialTheme.typography.titleMedium, diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt new file mode 100644 index 00000000..c87d6418 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/navigation/UserInfoNavigation.kt @@ -0,0 +1,75 @@ +package com.squirtles.musicroad.userinfo.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.squirtles.musicroad.mypick.MyPickScreen +import com.squirtles.musicroad.navigation.MainRoute +import com.squirtles.musicroad.navigation.UserInfoRoute +import com.squirtles.musicroad.userinfo.screen.EditNotificationSettingScreen +import com.squirtles.musicroad.userinfo.screen.EditProfileScreen +import com.squirtles.musicroad.userinfo.screen.UserInfoScreen + +fun NavController.navigateUserInfo(userId: String, navOptions: NavOptions? = null) { + navigate(MainRoute.UserInfo(userId), navOptions) +} + +fun NavController.navigateEditProfile(navOptions: NavOptions? = null) { + navigate(UserInfoRoute.EditProfile, navOptions) +} + +fun NavController.navigateEditNotificationSetting(navOptions: NavOptions? = null) { + navigate(UserInfoRoute.EditNotification, navOptions) +} + +fun NavController.navigateMyPicks(userId: String, navOptions: NavOptions) { + navigate(UserInfoRoute.MyPicks(userId), navOptions) +} + +fun NavGraphBuilder.userInfoNavGraph( + onBackClick: () -> Unit, + onItemClick: (String) -> Unit, + onBackToMapClick: () -> Unit, + onFavoritePicksClick: (String) -> Unit, + onMyPicksClick: (String) -> Unit, + onEditProfileClick: () -> Unit, + onEditNotificationClick: () -> Unit, +) { + composable { backStackEntry -> + val userId = backStackEntry.toRoute().userId + + UserInfoScreen( + userId = userId, + onBackClick = onBackClick, + onBackToMapClick = onBackToMapClick, + onFavoritePicksClick = onFavoritePicksClick, + onMyPicksClick = onMyPicksClick, + onEditProfileClick = onEditProfileClick, + onEditNotificationClick = onEditNotificationClick, + ) + } + + composable { + EditProfileScreen( + onBackClick = onBackClick, + ) + } + + composable { + EditNotificationSettingScreen( + onBackClick = onBackClick + ) + } + + composable { backStackEntry -> + val userId = backStackEntry.toRoute().userId + + MyPickScreen( + userId = userId, + onBackClick = onBackClick, + onItemClick = onItemClick + ) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/setting/SettingNotificationScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt similarity index 94% rename from app/src/main/java/com/squirtles/musicroad/setting/SettingNotificationScreen.kt rename to app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt index 18880510..fb3a55c3 100644 --- a/app/src/main/java/com/squirtles/musicroad/setting/SettingNotificationScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditNotificationSettingScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.setting +package com.squirtles.musicroad.userinfo.screen import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -19,7 +19,7 @@ import com.squirtles.musicroad.common.DefaultTopAppBar import com.squirtles.musicroad.ui.theme.White @Composable -internal fun SettingNotificationScreen( +internal fun EditNotificationSettingScreen( onBackClick: () -> Unit ) { Scaffold( diff --git a/app/src/main/java/com/squirtles/musicroad/setting/SettingProfileScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt similarity index 91% rename from app/src/main/java/com/squirtles/musicroad/setting/SettingProfileScreen.kt rename to app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt index 8d6a3124..322b5a78 100644 --- a/app/src/main/java/com/squirtles/musicroad/setting/SettingProfileScreen.kt +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/EditProfileScreen.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.setting +package com.squirtles.musicroad.userinfo.screen import android.content.Context import android.widget.Toast @@ -51,30 +51,30 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.flowWithLifecycle import com.squirtles.musicroad.R import com.squirtles.musicroad.common.Constants.COLOR_STOPS -import com.squirtles.musicroad.profile.ProfileViewModel import com.squirtles.musicroad.ui.theme.Black import com.squirtles.musicroad.ui.theme.Gray import com.squirtles.musicroad.ui.theme.MusicRoadTheme import com.squirtles.musicroad.ui.theme.White +import com.squirtles.musicroad.userinfo.UserInfoViewModel import kotlinx.coroutines.delay import java.util.regex.Pattern @Composable -internal fun SettingProfileScreen( +internal fun EditProfileScreen( onBackClick: () -> Unit, - profileViewModel: ProfileViewModel = hiltViewModel() + userInfoViewModel: UserInfoViewModel = hiltViewModel() ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val focusManager = LocalFocusManager.current - val userName = remember { mutableStateOf(profileViewModel.currentUser?.userName ?: "") } + val userName = remember { mutableStateOf(userInfoViewModel.currentUser?.userName ?: "") } val nickNameErrorMessage = remember { mutableStateOf("") } var showCreateIndicator by rememberSaveable { mutableStateOf(false) } BackHandler(enabled = showCreateIndicator) { } LaunchedEffect(Unit) { - profileViewModel.updateSuccess + userInfoViewModel.updateSuccess .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) .collect { isSuccess -> focusManager.clearFocus() @@ -99,12 +99,12 @@ internal fun SettingProfileScreen( Scaffold( topBar = { - SettingProfileAppBar( + EditProfileAppBar( confirmEnabled = nickNameErrorMessage.value.isEmpty() && - profileViewModel.currentUser?.userName != userName.value, + userInfoViewModel.currentUser?.userName != userName.value, onConfirmClick = { showCreateIndicator = true - profileViewModel.updateUsername(userName.value) + userInfoViewModel.updateUsername(userName.value) }, onBackClick = onBackClick ) @@ -116,7 +116,7 @@ internal fun SettingProfileScreen( .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) .padding(innerPadding) ) { - SettingProfileContent(userName, nickNameErrorMessage) + EditProfileContents(userName, nickNameErrorMessage) } } @@ -139,7 +139,7 @@ internal fun SettingProfileScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun SettingProfileAppBar( +private fun EditProfileAppBar( confirmEnabled: Boolean, onConfirmClick: () -> Unit, onBackClick: () -> Unit @@ -189,7 +189,7 @@ private fun validateUserName(userName: String, context: Context) = when { } @Composable -private fun SettingProfileContent( +private fun EditProfileContents( userName: MutableState, nickNameErrorMessage: MutableState ) { @@ -236,15 +236,15 @@ private const val USERNAME_PATTERN = "^[ㄱ-ㅎ|ㅏ-ㅣ가-힣a-zA-Z0-9]+$" @Preview @Composable -private fun SettingProfileAppBarPreview() { - SettingProfileAppBar(false, {}, {}) +private fun EditProfileAppBarPreview() { + EditProfileAppBar(false, {}, {}) } @Preview @Composable -private fun SettingProfileContentPreview() { +private fun EditProfileContentPreview() { MusicRoadTheme { - SettingProfileContent( + EditProfileContents( remember { mutableStateOf("짱구") }, remember { mutableStateOf("") } ) diff --git a/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt new file mode 100644 index 00000000..a4edbce2 --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/userinfo/screen/UserInfoScreen.kt @@ -0,0 +1,243 @@ +package com.squirtles.musicroad.userinfo.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Logout +import androidx.compose.material.icons.filled.MusicNote +import androidx.compose.material.icons.outlined.Archive +import androidx.compose.material.icons.outlined.Map +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.SwitchAccount +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.flowWithLifecycle +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.squirtles.musicroad.R +import com.squirtles.musicroad.account.AccountViewModel +import com.squirtles.musicroad.account.GoogleId +import com.squirtles.musicroad.common.Constants.COLOR_STOPS +import com.squirtles.musicroad.common.DefaultTopAppBar +import com.squirtles.musicroad.common.DialogTextButton +import com.squirtles.musicroad.common.HorizontalSpacer +import com.squirtles.musicroad.common.MessageAlertDialog +import com.squirtles.musicroad.common.VerticalSpacer +import com.squirtles.musicroad.ui.theme.Primary +import com.squirtles.musicroad.ui.theme.White +import com.squirtles.musicroad.userinfo.UserInfoViewModel +import com.squirtles.musicroad.userinfo.components.MenuItem +import com.squirtles.musicroad.userinfo.components.UserInfoMenus + +@Composable +fun UserInfoScreen( + userId: String, + onBackClick: () -> Unit, + onBackToMapClick: () -> Unit, + onFavoritePicksClick: (String) -> Unit, + onMyPicksClick: (String) -> Unit, + onEditProfileClick: () -> Unit, + onEditNotificationClick: () -> Unit, + userInfoViewModel: UserInfoViewModel = hiltViewModel(), + accountViewModel: AccountViewModel = hiltViewModel() +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val scrollState = rememberScrollState() + val user by userInfoViewModel.profileUser.collectAsStateWithLifecycle() + + var showLogOutDialog by remember { mutableStateOf(false) } + + val onSignOutClick: () -> Unit = { + GoogleId(context).signOut() + accountViewModel.signOut() + } + + LaunchedEffect(Unit) { + userId?.let { + userInfoViewModel.getUserById(userId) + } + + accountViewModel.signOutSuccess + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { isSuccess -> + if (isSuccess) { + onBackToMapClick() + } + } + } + + Scaffold( + topBar = { + DefaultTopAppBar( + title = if (userId == null) stringResource(id = R.string.profile_sign_in_title) else user.userName, + onBackClick = onBackClick + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = COLOR_STOPS)) + .padding(innerPadding), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(bottom = 96.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + VerticalSpacer(16) + + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(user.userProfileImage) + .crossfade(true) + .build(), + contentDescription = stringResource(R.string.user_info_profile_image), + modifier = Modifier + .size(180.dp) + .clip(CircleShape), + placeholder = painterResource(R.drawable.img_user_default_profile), + error = painterResource(R.drawable.img_user_default_profile), + contentScale = ContentScale.Crop, + ) + + VerticalSpacer(40) + + UserInfoMenus( + title = stringResource(R.string.user_info_pick_category_title), + menus = listOf( + MenuItem( + imageVector = Icons.Outlined.Archive, + contentDescription = stringResource(R.string.user_info_favorite_menu_icon_description), + menuTitle = stringResource(R.string.user_info_favorite_menu_title), + onMenuClick = { onFavoritePicksClick(userId) } + ), + MenuItem( + imageVector = Icons.Default.MusicNote, + contentDescription = stringResource(R.string.user_info_created_by_self_menu_icon_description), + menuTitle = stringResource(R.string.user_info_created_by_self_menu_title), + onMenuClick = { onMyPicksClick(userId) } + ) + ) + ) + + if (userId == userInfoViewModel.currentUser?.userId) { + UserInfoMenus( + title = stringResource(R.string.user_info_setting_category_title), + menus = listOf( + MenuItem( + imageVector = Icons.Outlined.SwitchAccount, + contentDescription = stringResource(R.string.user_info_setting_profile_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_profile_menu_title), + onMenuClick = onEditProfileClick + ), + MenuItem( + imageVector = Icons.Outlined.Notifications, + contentDescription = stringResource(R.string.user_info_setting_notification_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_notification_menu_title), + onMenuClick = onEditNotificationClick + ), + MenuItem( + imageVector = Icons.AutoMirrored.Outlined.Logout, + contentDescription = stringResource(R.string.user_info_setting_sign_out_menu_icon_description), + menuTitle = stringResource(R.string.user_info_setting_sign_out_menu_title), + onMenuClick = { showLogOutDialog = true } + ) + ) + ) + } + } + + ExtendedFloatingActionButton( + onClick = onBackToMapClick, + modifier = Modifier + .wrapContentWidth() + .padding(horizontal = 8.dp) + .padding(bottom = 48.dp) + .align(Alignment.BottomCenter), + shape = CircleShape, + containerColor = Primary, + contentColor = White, + ) { + Icon( + imageVector = Icons.Outlined.Map, + contentDescription = stringResource(R.string.user_info_icon_map_description), + tint = White + ) + + HorizontalSpacer(8) + + Text( + text = stringResource(R.string.user_info_back_to_map_button_text), + color = White, + style = MaterialTheme.typography.bodyLarge + ) + } + + if (showLogOutDialog) { + MessageAlertDialog( + onDismissRequest = { + showLogOutDialog = false + }, + title = stringResource(R.string.sign_out_dialog_title), + body = "", + showBody = false + ) { + DialogTextButton( + onClick = { + showLogOutDialog = false + }, + text = stringResource(R.string.sign_out_dialog_dismiss) + ) + + HorizontalSpacer(8) + + DialogTextButton( + onClick = { + showLogOutDialog = false + onSignOutClick() + }, + text = stringResource(R.string.sign_out_dialog_confirm), + textColor = Primary, + fontWeight = FontWeight.Bold + ) + } + } + } + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt b/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt new file mode 100644 index 00000000..3741f31c --- /dev/null +++ b/app/src/main/java/com/squirtles/musicroad/utils/SerializableType.kt @@ -0,0 +1,22 @@ +package com.squirtles.musicroad.utils + +import android.os.Bundle +import androidx.navigation.NavType +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +inline fun serializableType( + isNullableAllowed: Boolean = false, + json: Json = Json, +) = object : NavType(isNullableAllowed = isNullableAllowed) { + override fun get(bundle: Bundle, key: String) = + bundle.getString(key)?.let(json::decodeFromString) + + override fun parseValue(value: String): T = json.decodeFromString(value) + + override fun serializeAsValue(value: T): String = json.encodeToString(value) + + override fun put(bundle: Bundle, key: String, value: T) { + bundle.putString(key, json.encodeToString(value)) + } +} diff --git a/app/src/main/java/com/squirtles/musicroad/common/ThrottleFirst.kt b/app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt similarity index 92% rename from app/src/main/java/com/squirtles/musicroad/common/ThrottleFirst.kt rename to app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt index b409ff23..cc768826 100644 --- a/app/src/main/java/com/squirtles/musicroad/common/ThrottleFirst.kt +++ b/app/src/main/java/com/squirtles/musicroad/utils/ThrottleFirst.kt @@ -1,4 +1,4 @@ -package com.squirtles.musicroad.common +package com.squirtles.musicroad.utils import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56d42289..8ad85596 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,6 +136,13 @@ 담은 픽을 확인하기 위해\n로그인이 필요합니다 픽을 등록하기 위해\n로그인이 필요합니다 픽을 담기 위해\n로그인이 필요합니다 + 픽을 담기 위해\n로그인이 필요합니다 + 로그인이 필요합니다 취소 기기에 로그인된 구글 계정이 없습니다 + + + 로그아웃 하시겠습니까? + 취소 + 로그아웃 diff --git a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt index 74fa0e88..9c421ec1 100644 --- a/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/local/LocalDataSourceImpl.kt @@ -5,7 +5,7 @@ import android.location.Location import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import com.squirtles.domain.datasource.LocalDataSource +import com.squirtles.domain.local.LocalDataSource import com.squirtles.domain.model.Order import com.squirtles.domain.model.User import kotlinx.coroutines.flow.Flow @@ -33,14 +33,14 @@ class LocalDataSourceImpl @Inject constructor( private var _myListOrder = Order.LATEST override val myListOrder get() = _myListOrder - override fun readUserId(): Flow { + override fun readUserIdDataStore(): Flow { val dataStoreKey = stringPreferencesKey(USER_ID_KEY) return context.dataStore.data.map { preferences -> preferences[dataStoreKey] } } - override suspend fun saveUserId(userId: String) { + override suspend fun saveUserIdDataStore(userId: String) { val dataStoreKey = stringPreferencesKey(USER_ID_KEY) context.dataStore.edit { preferences -> preferences[dataStoreKey] = userId diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt index 6e6c5c70..38d8f8f4 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/applemusic/AppleMusicDataSourceImpl.kt @@ -7,7 +7,7 @@ import com.squirtles.data.datasource.remote.applemusic.SearchSongsPagingSource.C import com.squirtles.data.datasource.remote.applemusic.api.AppleMusicApi import com.squirtles.data.datasource.remote.applemusic.model.SearchResponse import com.squirtles.data.mapper.toMusicVideo -import com.squirtles.domain.datasource.AppleMusicRemoteDataSource +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song import kotlinx.coroutines.flow.Flow diff --git a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt index ab1b2070..41b205f7 100644 --- a/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt +++ b/data/src/main/java/com/squirtles/data/datasource/remote/firebase/FirebaseDataSourceImpl.kt @@ -18,7 +18,7 @@ import com.squirtles.data.datasource.remote.firebase.model.FirebaseUser import com.squirtles.data.mapper.toFirebasePick import com.squirtles.data.mapper.toPick import com.squirtles.data.mapper.toUser -import com.squirtles.domain.datasource.FirebaseRemoteDataSource +import com.squirtles.domain.firebase.FirebaseRemoteDataSource import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User import kotlinx.coroutines.CoroutineScope diff --git a/data/src/main/java/com/squirtles/data/di/DataModule.kt b/data/src/main/java/com/squirtles/data/di/DataModule.kt index 55c8d128..e3363344 100644 --- a/data/src/main/java/com/squirtles/data/di/DataModule.kt +++ b/data/src/main/java/com/squirtles/data/di/DataModule.kt @@ -9,12 +9,12 @@ import com.squirtles.data.datasource.remote.firebase.FirebaseDataSourceImpl import com.squirtles.data.repository.AppleMusicRepositoryImpl import com.squirtles.data.repository.FirebaseRepositoryImpl import com.squirtles.data.repository.LocalRepositoryImpl -import com.squirtles.domain.datasource.AppleMusicRemoteDataSource -import com.squirtles.domain.datasource.FirebaseRemoteDataSource -import com.squirtles.domain.datasource.LocalDataSource -import com.squirtles.domain.repository.AppleMusicRepository -import com.squirtles.domain.repository.FirebaseRepository -import com.squirtles.domain.repository.LocalRepository +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.local.LocalDataSource +import com.squirtles.domain.applemusic.AppleMusicRepository +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.local.LocalRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt index 90b8b0fd..1b8c20a7 100644 --- a/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/AppleMusicRepositoryImpl.kt @@ -1,11 +1,11 @@ package com.squirtles.data.repository import androidx.paging.PagingData -import com.squirtles.domain.datasource.AppleMusicRemoteDataSource -import com.squirtles.domain.exception.AppleMusicException +import com.squirtles.domain.applemusic.AppleMusicRemoteDataSource +import com.squirtles.domain.applemusic.AppleMusicException import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.repository.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject diff --git a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt index 31d9ab39..3ef4f710 100644 --- a/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/FirebaseRepositoryImpl.kt @@ -1,10 +1,10 @@ package com.squirtles.data.repository -import com.squirtles.domain.datasource.FirebaseRemoteDataSource -import com.squirtles.domain.exception.FirebaseException +import com.squirtles.domain.firebase.FirebaseRemoteDataSource +import com.squirtles.domain.firebase.FirebaseException import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt b/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt index 2651d204..c698674f 100644 --- a/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt +++ b/data/src/main/java/com/squirtles/data/repository/LocalRepositoryImpl.kt @@ -1,23 +1,27 @@ package com.squirtles.data.repository import android.location.Location -import com.squirtles.domain.datasource.LocalDataSource +import com.squirtles.domain.local.LocalDataSource +import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.Order import com.squirtles.domain.model.User -import com.squirtles.domain.repository.LocalRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class LocalRepositoryImpl @Inject constructor( private val localDataSource: LocalDataSource, ) : LocalRepository { - override val userId get() = localDataSource.readUserId() override val currentUser get() = localDataSource.currentUser override val lastLocation get() = localDataSource.lastLocation override val favoriteListOrder get() = localDataSource.favoriteListOrder override val myListOrder get() = localDataSource.myListOrder - override suspend fun saveUserId(userId: String) { - localDataSource.saveUserId(userId) + override fun readUserIdDataStore(): Flow { + return localDataSource.readUserIdDataStore() + } + + override suspend fun saveUserIdDataStore(userId: String) { + localDataSource.saveUserIdDataStore(userId) } override suspend fun saveCurrentUser(user: User) { diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 304b69ea..5255e9d6 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) + alias(libs.plugins.kotlin.serialization) } android { @@ -51,4 +52,7 @@ dependencies { implementation(libs.androidx.media3.common) implementation(libs.androidx.media3.session) + + // Serialization + implementation(libs.kotlinx.serialization.json) } diff --git a/domain/src/main/java/com/squirtles/domain/exception/AppleMusicException.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt similarity index 91% rename from domain/src/main/java/com/squirtles/domain/exception/AppleMusicException.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt index a70b6e94..779ea832 100644 --- a/domain/src/main/java/com/squirtles/domain/exception/AppleMusicException.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.exception +package com.squirtles.domain.applemusic /** * 400 에러가 여러 종류가 있는데 이를 구분할 용도로 만든 예외 클래스 diff --git a/domain/src/main/java/com/squirtles/domain/datasource/AppleMusicRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt similarity index 90% rename from domain/src/main/java/com/squirtles/domain/datasource/AppleMusicRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt index 70c1db7a..752dd499 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/AppleMusicRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource +package com.squirtles.domain.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/repository/AppleMusicRepository.kt b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt similarity index 90% rename from domain/src/main/java/com/squirtles/domain/repository/AppleMusicRepository.kt rename to domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt index 4b5fb745..4df96e51 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/AppleMusicRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/applemusic/AppleMusicRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository +package com.squirtles.domain.applemusic import androidx.paging.PagingData import com.squirtles.domain.model.MusicVideo diff --git a/domain/src/main/java/com/squirtles/domain/exception/FirebaseException.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt similarity index 93% rename from domain/src/main/java/com/squirtles/domain/exception/FirebaseException.kt rename to domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt index 81679d0b..946d7310 100644 --- a/domain/src/main/java/com/squirtles/domain/exception/FirebaseException.kt +++ b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseException.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.exception +package com.squirtles.domain.firebase sealed class FirebaseException(override val message: String) : Exception() { data class CreatedUserFailedException(override val message: String = "Failed to create a user") : FirebaseException(message) diff --git a/domain/src/main/java/com/squirtles/domain/datasource/FirebaseRemoteDataSource.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt similarity index 96% rename from domain/src/main/java/com/squirtles/domain/datasource/FirebaseRemoteDataSource.kt rename to domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt index 5b040b3b..305f6d9e 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/FirebaseRemoteDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRemoteDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource +package com.squirtles.domain.firebase import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User diff --git a/domain/src/main/java/com/squirtles/domain/repository/FirebaseRepository.kt b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt similarity index 96% rename from domain/src/main/java/com/squirtles/domain/repository/FirebaseRepository.kt rename to domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt index 846cd5d6..6e4e0f10 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/FirebaseRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/firebase/FirebaseRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository +package com.squirtles.domain.firebase import com.squirtles.domain.model.Pick import com.squirtles.domain.model.User diff --git a/domain/src/main/java/com/squirtles/domain/datasource/LocalDataSource.kt b/domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt similarity index 81% rename from domain/src/main/java/com/squirtles/domain/datasource/LocalDataSource.kt rename to domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt index 46545314..9999d3b3 100644 --- a/domain/src/main/java/com/squirtles/domain/datasource/LocalDataSource.kt +++ b/domain/src/main/java/com/squirtles/domain/local/LocalDataSource.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.datasource +package com.squirtles.domain.local import android.location.Location import com.squirtles.domain.model.Order @@ -12,8 +12,8 @@ interface LocalDataSource { val favoriteListOrder: Order val myListOrder: Order - fun readUserId(): Flow - suspend fun saveUserId(userId: String) + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) suspend fun saveCurrentUser(user: User) suspend fun clearUser() suspend fun saveCurrentLocation(location: Location) diff --git a/domain/src/main/java/com/squirtles/domain/repository/LocalRepository.kt b/domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt similarity index 82% rename from domain/src/main/java/com/squirtles/domain/repository/LocalRepository.kt rename to domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt index eeb97e4e..fe3402ad 100644 --- a/domain/src/main/java/com/squirtles/domain/repository/LocalRepository.kt +++ b/domain/src/main/java/com/squirtles/domain/local/LocalRepository.kt @@ -1,4 +1,4 @@ -package com.squirtles.domain.repository +package com.squirtles.domain.local import android.location.Location import com.squirtles.domain.model.Order @@ -7,13 +7,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface LocalRepository { - val userId: Flow // 기기에 저장된 userId val currentUser: User? val lastLocation: StateFlow val favoriteListOrder: Order // 픽 보관함 정렬 순서 val myListOrder: Order // 등록한 픽 정렬 순서 - suspend fun saveUserId(userId: String) + fun readUserIdDataStore(): Flow + suspend fun saveUserIdDataStore(userId: String) suspend fun saveCurrentUser(user: User) suspend fun clearUser(): Result suspend fun saveCurrentLocation(location: Location) diff --git a/domain/src/main/java/com/squirtles/domain/model/Song.kt b/domain/src/main/java/com/squirtles/domain/model/Song.kt index 04faad64..b80007df 100644 --- a/domain/src/main/java/com/squirtles/domain/model/Song.kt +++ b/domain/src/main/java/com/squirtles/domain/model/Song.kt @@ -1,11 +1,11 @@ package com.squirtles.domain.model -import android.util.Size -import androidx.annotation.ColorInt +import kotlinx.serialization.Serializable /** * 애플뮤직에서 불러온 노래 정보를 비즈니스 로직에서 사용하기 위해 변환한 클래스 */ +@Serializable data class Song( val id: String, val songName: String, @@ -13,13 +13,13 @@ data class Song( val albumName: String, val imageUrl: String, val genreNames: List, - @ColorInt val bgColor: Int, + val bgColor: Int, val externalUrl: String, val previewUrl: String, ) { - fun getImageUrlWithSize(size: Size): String? { + fun getImageUrlWithSize(width: Int, height: Int): String? { return if (imageUrl.isEmpty()) null - else imageUrl.replace("{w}", size.width.toString()) - .replace("{h}", size.height.toString()) + else imageUrl.replace("{w}", width.toString()) + .replace("{h}", height.toString()) } } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/CreateFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt similarity index 70% rename from domain/src/main/java/com/squirtles/domain/usecase/favoritepick/CreateFavoriteUseCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt index 2977ddd2..93292a95 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/CreateFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/CreateFavoriteUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.favoritepick +package com.squirtles.domain.usecase.favorite -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class CreateFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt new file mode 100644 index 00000000..e2a485f9 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/DeleteFavoriteUseCase.kt @@ -0,0 +1,12 @@ +package com.squirtles.domain.usecase.favorite + +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface +import javax.inject.Inject + +class DeleteFavoriteUseCase @Inject constructor( + private val firebaseRepository: FirebaseRepository +) : DeletePickListUseCaseInterface { + override suspend operator fun invoke(pickId: String, userId: String) = + firebaseRepository.deleteFavorite(pickId, userId) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt new file mode 100644 index 00000000..ab3b8459 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/favorite/FetchFavoritePicksUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.usecase.favorite + +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface +import javax.inject.Inject + +class FetchFavoritePicksUseCase @Inject constructor( + private val firebaseRepository: FirebaseRepository +) : FetchPickListUseCaseInterface { + override suspend operator fun invoke(userId: String) = firebaseRepository.fetchFavoritePicks(userId) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/DeleteFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/DeleteFavoriteUseCase.kt deleted file mode 100644 index 4185b862..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/DeleteFavoriteUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.favoritepick - -import com.squirtles.domain.repository.FirebaseRepository -import javax.inject.Inject - -class DeleteFavoriteUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(pickId: String, userId: String) = - firebaseRepository.deleteFavorite(pickId, userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/FetchFavoritePicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/FetchFavoritePicksUseCase.kt deleted file mode 100644 index 5d370096..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/favoritepick/FetchFavoritePicksUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.usecase.favoritepick - -import com.squirtles.domain.repository.FirebaseRepository -import javax.inject.Inject - -class FetchFavoritePicksUseCase @Inject constructor( - private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(userId: String) = firebaseRepository.fetchFavoritePicks(userId) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/FetchLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/FetchLastLocationUseCase.kt deleted file mode 100644 index 3267e274..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/FetchLastLocationUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class FetchLastLocationUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - operator fun invoke() = localRepository.lastLocation -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/GetFavoriteListOrderUseCase.kt deleted file mode 100644 index afd4e149..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/GetFavoriteListOrderUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class GetFavoriteListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - operator fun invoke() = localRepository.favoriteListOrder -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/GetMyListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/GetMyListOrderUseCase.kt deleted file mode 100644 index 53aab216..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/GetMyListOrderUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class GetMyListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - operator fun invoke() = localRepository.myListOrder -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/GetUserIdFromLocalStorageUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/GetUserIdFromLocalStorageUseCase.kt deleted file mode 100644 index 9d7da975..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/GetUserIdFromLocalStorageUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class GetUserIdFromLocalStorageUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - operator fun invoke() = localRepository.userId -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/SaveFavoriteListOrderUseCase.kt deleted file mode 100644 index 188536bd..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveFavoriteListOrderUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.model.Order -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class SaveFavoriteListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - suspend operator fun invoke(order: Order) = localRepository.saveFavoriteListOrder(order) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveMyListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/local/SaveMyListOrderUseCase.kt deleted file mode 100644 index 2cd36e0e..00000000 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveMyListOrderUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.squirtles.domain.usecase.local - -import com.squirtles.domain.model.Order -import com.squirtles.domain.repository.LocalRepository -import javax.inject.Inject - -class SaveMyListOrderUseCase @Inject constructor( - private val localRepository: LocalRepository -) { - suspend operator fun invoke(order: Order) = localRepository.saveMyListOrder(order) -} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt new file mode 100644 index 00000000..d081a9d9 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/GetLastLocationUseCase.kt @@ -0,0 +1,10 @@ +package com.squirtles.domain.usecase.location + +import com.squirtles.domain.local.LocalRepository +import javax.inject.Inject + +class GetLastLocationUseCase @Inject constructor( + private val localRepository: LocalRepository +) { + operator fun invoke() = localRepository.lastLocation +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveLastLocationUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt similarity index 73% rename from domain/src/main/java/com/squirtles/domain/usecase/local/SaveLastLocationUseCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt index 8c0d4a0e..8e333af9 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/SaveLastLocationUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/location/SaveLastLocationUseCase.kt @@ -1,7 +1,7 @@ -package com.squirtles.domain.usecase.local +package com.squirtles.domain.usecase.location import android.location.Location -import com.squirtles.domain.repository.LocalRepository +import com.squirtles.domain.local.LocalRepository import javax.inject.Inject class SaveLastLocationUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt index 0405d30d..1f42c35b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchMusicVideoUseCase.kt @@ -2,7 +2,7 @@ package com.squirtles.domain.usecase.music import com.squirtles.domain.model.MusicVideo import com.squirtles.domain.model.Song -import com.squirtles.domain.repository.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import javax.inject.Inject class FetchMusicVideoUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/music/SearchSongsUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt similarity index 68% rename from domain/src/main/java/com/squirtles/domain/usecase/music/SearchSongsUseCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt index 2110c21a..54370883 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/music/SearchSongsUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/music/FetchSongsUseCase.kt @@ -1,9 +1,9 @@ package com.squirtles.domain.usecase.music -import com.squirtles.domain.repository.AppleMusicRepository +import com.squirtles.domain.applemusic.AppleMusicRepository import javax.inject.Inject -class SearchSongsUseCase @Inject constructor( +class FetchSongsUseCase @Inject constructor( private val appleMusicRepository: AppleMusicRepository ) { operator fun invoke(searchText: String) = appleMusicRepository.searchSongs(searchText) diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt index 0e6fef0d..f0816446 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/mypick/CreatePickUseCase.kt @@ -1,7 +1,7 @@ package com.squirtles.domain.usecase.mypick import com.squirtles.domain.model.Pick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class CreatePickUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt index 62984c36..98985e75 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/mypick/DeletePickUseCase.kt @@ -1,11 +1,12 @@ package com.squirtles.domain.usecase.mypick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.usecase.picklist.DeletePickListUseCaseInterface import javax.inject.Inject class DeletePickUseCase @Inject constructor( private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(pickId: String, userId: String): Result = +) : DeletePickListUseCaseInterface { + override suspend operator fun invoke(pickId: String, userId: String): Result = firebaseRepository.deletePick(pickId, userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt index b435985a..4774ad82 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/mypick/FetchMyPicksUseCase.kt @@ -1,11 +1,12 @@ package com.squirtles.domain.usecase.mypick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.usecase.picklist.FetchPickListUseCaseInterface import javax.inject.Inject class FetchMyPicksUseCase @Inject constructor( private val firebaseRepository: FirebaseRepository -) { - suspend operator fun invoke(userId: String) = +) : FetchPickListUseCaseInterface { + override suspend operator fun invoke(userId: String) = firebaseRepository.fetchMyPicks(userId) } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt new file mode 100644 index 00000000..98a20b6a --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetFavoriteListOrderUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.usecase.order + +import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import javax.inject.Inject + +class GetFavoriteListOrderUseCase @Inject constructor( + private val localRepository: LocalRepository +) : GetPickListOrderUseCaseInterface { + override suspend operator fun invoke() = localRepository.favoriteListOrder +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt new file mode 100644 index 00000000..b41e7d66 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/GetMyPickListOrderUseCase.kt @@ -0,0 +1,11 @@ +package com.squirtles.domain.usecase.order + +import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.usecase.picklist.GetPickListOrderUseCaseInterface +import javax.inject.Inject + +class GetMyPickListOrderUseCase @Inject constructor( + private val localRepository: LocalRepository +) : GetPickListOrderUseCaseInterface { + override suspend operator fun invoke() = localRepository.myListOrder +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt new file mode 100644 index 00000000..8b7ed05f --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveFavoriteListOrderUseCase.kt @@ -0,0 +1,12 @@ +package com.squirtles.domain.usecase.order + +import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.model.Order +import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface +import javax.inject.Inject + +class SaveFavoriteListOrderUseCase @Inject constructor( + private val localRepository: LocalRepository +) : SavePickListOrderUseCaseInterface { + override suspend operator fun invoke(order: Order) = localRepository.saveFavoriteListOrder(order) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt new file mode 100644 index 00000000..0ca672d7 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/order/SaveMyPickListOrderUseCase.kt @@ -0,0 +1,12 @@ +package com.squirtles.domain.usecase.order + +import com.squirtles.domain.local.LocalRepository +import com.squirtles.domain.model.Order +import com.squirtles.domain.usecase.picklist.SavePickListOrderUseCaseInterface +import javax.inject.Inject + +class SaveMyPickListOrderUseCase @Inject constructor( + private val localRepository: LocalRepository +) : SavePickListOrderUseCaseInterface { + override suspend operator fun invoke(order: Order) = localRepository.saveMyListOrder(order) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt index b9ab367b..41ff0315 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchIsFavoriteUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class FetchIsFavoriteUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt index ef403d2f..053d230e 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickInAreaUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class FetchPickInAreaUseCase @Inject constructor( @@ -8,4 +8,4 @@ class FetchPickInAreaUseCase @Inject constructor( ) { suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = firebaseRepository.fetchPicksInArea(lat, lng, radiusInM) -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt index 7c770cc6..37c7e62b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/pick/FetchPickUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.pick -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class FetchPickUseCase @Inject constructor( @@ -8,4 +8,7 @@ class FetchPickUseCase @Inject constructor( ) { suspend operator fun invoke(pickId: String) = firebaseRepository.fetchPick(pickId) -} \ No newline at end of file + + suspend operator fun invoke(lat: Double, lng: Double, radiusInM: Double) = + firebaseRepository.fetchPicksInArea(lat, lng, radiusInM) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt new file mode 100644 index 00000000..3a7a58ed --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/picklist/DeletePickListUseCaseInterface.kt @@ -0,0 +1,5 @@ +package com.squirtles.domain.usecase.picklist + +interface DeletePickListUseCaseInterface { + suspend operator fun invoke(pickId: String, userId: String): Result +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt new file mode 100644 index 00000000..5b61faf7 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/picklist/FetchPickListUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.squirtles.domain.usecase.picklist + +import com.squirtles.domain.model.Pick + +interface FetchPickListUseCaseInterface { + suspend operator fun invoke(userId: String): Result> +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt new file mode 100644 index 00000000..ad98b854 --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/picklist/GetPickListOrderUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.squirtles.domain.usecase.picklist + +import com.squirtles.domain.model.Order + +interface GetPickListOrderUseCaseInterface { + suspend operator fun invoke(): Order +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt b/domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt new file mode 100644 index 00000000..eb04b50e --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/picklist/SavePickListOrderUseCaseInterface.kt @@ -0,0 +1,7 @@ +package com.squirtles.domain.usecase.picklist + +import com.squirtles.domain.model.Order + +interface SavePickListOrderUseCaseInterface { + suspend operator fun invoke(order: Order) +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt index 7f26781a..7f89fe9f 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/ClearUserUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.repository.LocalRepository +import com.squirtles.domain.local.LocalRepository import javax.inject.Inject class ClearUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt index f16f6e63..f0f1a9d0 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/CreateGoogleIdUserUseCase.kt @@ -1,8 +1,8 @@ package com.squirtles.domain.usecase.user +import com.squirtles.domain.firebase.FirebaseRepository +import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.User -import com.squirtles.domain.repository.FirebaseRepository -import com.squirtles.domain.repository.LocalRepository import javax.inject.Inject class CreateGoogleIdUserUseCase @Inject constructor( @@ -17,7 +17,7 @@ class CreateGoogleIdUserUseCase @Inject constructor( val createdUser = firebaseRepository.createGoogleIdUser(userId, userName, userProfileImage) .onSuccess { user -> // 생성된 유저의 userId 저장 후 user 반환 - localRepository.saveUserId(user.userId) + localRepository.saveUserIdDataStore(user.userId) localRepository.saveCurrentUser(user) } return createdUser diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt index d1a91c89..780d551b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserByIdUseCase.kt @@ -1,6 +1,6 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject class FetchUserByIdUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt index a931e3d1..c6e755d5 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/FetchUserUseCase.kt @@ -1,20 +1,18 @@ package com.squirtles.domain.usecase.user +import com.squirtles.domain.local.LocalRepository import com.squirtles.domain.model.User -import com.squirtles.domain.repository.FirebaseRepository -import com.squirtles.domain.repository.LocalRepository import javax.inject.Inject class FetchUserUseCase @Inject constructor( private val localRepository: LocalRepository, - private val firebaseRepository: FirebaseRepository + private val fetchUserByIdUseCase: FetchUserByIdUseCase ) { suspend operator fun invoke(userId: String): Result { - // userId가 있으면 Firestore에서 유저 가져오기 - val user = firebaseRepository.fetchUser(userId) + val user = fetchUserByIdUseCase(userId) // userId가 있으면 Firestore에서 유저 가져오기 .onSuccess { user -> - localRepository.saveUserId(user.userId) - localRepository.saveCurrentUser(user) + localRepository.saveUserIdDataStore(user.userId) + localRepository.saveCurrentUser(user) // Firestore에서 가져온 user를 LocalDataSource에 저장 } return user } diff --git a/domain/src/main/java/com/squirtles/domain/usecase/local/GetCurrentUserUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt similarity index 65% rename from domain/src/main/java/com/squirtles/domain/usecase/local/GetCurrentUserUseCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt index f713a2a6..e9d77326 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/local/GetCurrentUserUseCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetCurrentUserUseCase.kt @@ -1,6 +1,6 @@ -package com.squirtles.domain.usecase.local +package com.squirtles.domain.usecase.user -import com.squirtles.domain.repository.LocalRepository +import com.squirtles.domain.local.LocalRepository import javax.inject.Inject class GetCurrentUserUseCase @Inject constructor( diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt new file mode 100644 index 00000000..0cfe6fec --- /dev/null +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/GetUserIdFromDataStoreUseCase.kt @@ -0,0 +1,10 @@ +package com.squirtles.domain.usecase.user + +import com.squirtles.domain.local.LocalRepository +import javax.inject.Inject + +class GetUserIdFromDataStoreUseCase @Inject constructor( + private val localRepository: LocalRepository +) { + operator fun invoke() = localRepository.readUserIdDataStore() +} diff --git a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUserCase.kt b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt similarity index 71% rename from domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUserCase.kt rename to domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt index bbd45c7b..a31c886b 100644 --- a/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUserCase.kt +++ b/domain/src/main/java/com/squirtles/domain/usecase/user/UpdateUserNameUseCase.kt @@ -1,9 +1,9 @@ package com.squirtles.domain.usecase.user -import com.squirtles.domain.repository.FirebaseRepository +import com.squirtles.domain.firebase.FirebaseRepository import javax.inject.Inject -class UpdateUserNameUserCase @Inject constructor( +class UpdateUserNameUseCase @Inject constructor( private val firebaseRepository: FirebaseRepository ) { suspend operator fun invoke(userId: String, newUserName: String) = diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a3a4b14..95059d48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ retrofit = "2.11.0" splashscreen = "1.0.1" uiViewbinding = "1.7.5" pagingComposeAndroid = "3.3.4" +kotlinxImmutable = "0.3.7" [libraries] # AndroidX @@ -77,6 +78,7 @@ androidx-compose-material = { group = "androidx.wear.compose", name = "compose-m googleid = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" } +kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" } # Firebase firebase-analytics = { module = "com.google.firebase:firebase-analytics" }