diff --git a/app/build.gradle b/app/build.gradle index 84f401f2..39887ae3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,8 +33,8 @@ android { applicationId "com.daily.dayo" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 20000 - versionName "2.0.0" + versionCode 21000 + versionName "2.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -102,7 +102,7 @@ dependencies { implementation project(':domain') implementation project(':data') - def sentry_version = "7.16.0" + def sentry_version = "8.24.0" def glide_version = "4.15.1" // Hilt @@ -114,8 +114,10 @@ dependencies { implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" // Sentry - implementation "io.sentry:sentry-android:$sentry_version" - implementation 'org.slf4j:slf4j-nop:1.7.25' + implementation platform("io.sentry:sentry-bom:$sentry_version") + implementation('io.sentry:sentry-android') + implementation('io.sentry:sentry-android-fragment') + implementation 'org.slf4j:slf4j-nop:2.0.17' // Firebase implementation 'com.google.firebase:firebase-crashlytics-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt index 0b6ca61e..b178e474 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt @@ -6,10 +6,11 @@ import retrofit2.http.Query interface SearchApiService { - @GET("/api/v1/search") + @GET("/api/v2/search") suspend fun requestSearchTag( @Query("tag") tag: String, - @Query("end") end: Int + @Query("end") end: Int, + @Query("order") order: String ): NetworkResponse @GET("/api/v1/search/member") diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt index 137aa329..41240eae 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt @@ -5,18 +5,20 @@ import androidx.paging.PagingState import daily.dayo.data.mapper.toSearch import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Search +import daily.dayo.domain.model.SearchOrder class SearchPagingSource( private val apiService: SearchApiService, private val size: Int, - private val tag: String + private val tag: String, + private val searchOrder: SearchOrder ) : PagingSource() { override suspend fun load( params: LoadParams ): LoadResult { val nextPageNumber = params.key ?: 0 - apiService.requestSearchTag(tag = tag, end = nextPageNumber).let { ApiResponse -> + apiService.requestSearchTag(tag = tag, end = nextPageNumber, order = searchOrder.toString()).let { ApiResponse -> return try { when (ApiResponse) { is NetworkResponse.Success -> { diff --git a/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt b/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt index ea8232b8..ea0cf721 100644 --- a/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt +++ b/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt @@ -7,13 +7,14 @@ import androidx.paging.PagingData import daily.dayo.data.datasource.local.SharedManager import daily.dayo.data.datasource.remote.search.SearchApiService import daily.dayo.data.datasource.remote.search.SearchFollowUserPagingSource -import daily.dayo.data.datasource.remote.search.SearchUserPagingSource import daily.dayo.data.datasource.remote.search.SearchPagingSource +import daily.dayo.data.datasource.remote.search.SearchUserPagingSource import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryDetail import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import daily.dayo.domain.repository.SearchRepository import kotlinx.coroutines.flow.Flow @@ -47,10 +48,11 @@ class SearchRepositoryImpl @Inject constructor( override fun updateSearchKeywordRecentList(keyword: String, requestSearchType: SearchHistoryType) { SharedManager(context = context).updateSearchHistory( SearchHistoryDetail( - history = keyword, - searchHistoryType = requestSearchType, - searchId = 0 - )) + history = keyword, + searchHistoryType = requestSearchType, + searchId = 0 + ) + ) } override fun requestSearchUser(nickname: String): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { @@ -61,35 +63,43 @@ class SearchRepositoryImpl @Inject constructor( SearchFollowUserPagingSource(searchApiService, SEARCH_PAGE_SIZE, nickname) }.flow - override fun requestSearchTag(tag: String): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { - SearchPagingSource(searchApiService, SEARCH_PAGE_SIZE, tag) + override fun requestSearchTag(tag: String, searchOrder: SearchOrder): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { + SearchPagingSource(searchApiService, SEARCH_PAGE_SIZE, tag, searchOrder) }.flow - override suspend fun requestSearchTotalCount(tag: String, end: Int, searchHistoryType: SearchHistoryType) : Int = - when (searchHistoryType) { - SearchHistoryType.TAG -> { - searchApiService.requestSearchTag(tag, end).let { ApiResponse -> - when(ApiResponse) { - is NetworkResponse.Success -> { - return ApiResponse.body!!.totalCount - } - else -> return 0 + override suspend fun requestSearchTotalCount( + tag: String, + end: Int, + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder + ): Int = when (searchHistoryType) { + SearchHistoryType.TAG -> { + searchApiService.requestSearchTag(tag, end, searchOrder.toString()).let { ApiResponse -> + when (ApiResponse) { + is NetworkResponse.Success -> { + return ApiResponse.body!!.totalCount } + + else -> return 0 } } - SearchHistoryType.USER -> { - searchApiService.requestSearchUser(tag, end).let { ApiResponse -> - when(ApiResponse) { - is NetworkResponse.Success -> { - return ApiResponse.body!!.totalCount - } - else -> return 0 + } + + SearchHistoryType.USER -> { + searchApiService.requestSearchUser(tag, end).let { ApiResponse -> + when (ApiResponse) { + is NetworkResponse.Success -> { + return ApiResponse.body!!.totalCount } + + else -> return 0 } } - else -> 0 } + else -> 0 + } + companion object { private const val SEARCH_PAGE_SIZE = 10 } diff --git a/domain/src/main/java/daily/dayo/domain/model/Search.kt b/domain/src/main/java/daily/dayo/domain/model/Search.kt index e0c2f799..7fefa270 100644 --- a/domain/src/main/java/daily/dayo/domain/model/Search.kt +++ b/domain/src/main/java/daily/dayo/domain/model/Search.kt @@ -27,4 +27,12 @@ data class SearchHistoryDetail( enum class SearchHistoryType { USER, TAG -} \ No newline at end of file +} + +enum class SearchOrder { + NEW, OLD; + + override fun toString(): String { + return name.lowercase() + } +} diff --git a/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt b/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt index e9d37df8..9d690f8b 100644 --- a/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt +++ b/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt @@ -4,11 +4,12 @@ import androidx.paging.PagingData import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import kotlinx.coroutines.flow.Flow interface SearchRepository { - fun requestSearchTag(tag: String): Flow> + fun requestSearchTag(tag: String, searchOrder: SearchOrder): Flow> fun requestSearchUser(nickname: String): Flow> fun requestSearchFollowUser(nickname: String): Flow> fun requestSearchKeywordRecentList(): SearchHistory @@ -22,6 +23,7 @@ interface SearchRepository { suspend fun requestSearchTotalCount( tag: String, end: Int, - searchHistoryType: SearchHistoryType + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder ): Int } \ No newline at end of file diff --git a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt index a22ffea5..c0d80ca8 100644 --- a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt +++ b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt @@ -1,10 +1,11 @@ package daily.dayo.domain.usecase.search +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.repository.SearchRepository import javax.inject.Inject class RequestSearchTagUseCase @Inject constructor( private val searchRepository: SearchRepository ) { - operator fun invoke(tag: String) = searchRepository.requestSearchTag(tag) + operator fun invoke(tag: String, searchOrder: SearchOrder) = searchRepository.requestSearchTag(tag, searchOrder) } \ No newline at end of file diff --git a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt index b6821421..c83e9cc5 100644 --- a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt +++ b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt @@ -1,12 +1,16 @@ package daily.dayo.domain.usecase.search import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.repository.SearchRepository import javax.inject.Inject class RequestSearchTotalCountUseCase @Inject constructor( private val searchRepository: SearchRepository ) { - suspend operator fun invoke(tag: String, searchHistoryType: SearchHistoryType) = - searchRepository.requestSearchTotalCount(tag, 0, searchHistoryType) + suspend operator fun invoke( + tag: String, + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder = SearchOrder.NEW + ) = searchRepository.requestSearchTotalCount(tag, 0, searchHistoryType, searchOrder) } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt index 678e8a4c..0c90dcb1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt +++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt @@ -45,7 +45,8 @@ class MainActivity : AppCompatActivity() { MainScreen( onAdRequest = { onRewardSuccess -> showAdIfAvailable(onRewardSuccess) - } + }, + onExit = { finish() } ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt index da8ebb45..ce527ee4 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/AccountScreen.kt @@ -1,23 +1,33 @@ package daily.dayo.presentation.screen.account -import android.annotation.SuppressLint +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.material.Scaffold +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost +import daily.dayo.presentation.theme.Dark import daily.dayo.presentation.view.dialog.getBottomSheetDialogState +import kotlinx.coroutines.launch -@SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun AccountScreen( @@ -25,43 +35,66 @@ internal fun AccountScreen( ) { val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } + var bottomSheetContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) } val bottomSheetState = getBottomSheetDialogState() - var bottomSheet: (@Composable () -> Unit)? by remember { mutableStateOf(null) } - val bottomSheetContent: (@Composable () -> Unit) -> Unit = { - bottomSheet = it + val bottomSheetDimAlpha by remember { + derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } } - Scaffold( - snackbarHost = { SnackbarHost(hostState = snackBarHostState) } - ) { - Scaffold( - bottomBar = { bottomSheet?.let { it() } } - ) { - Scaffold( - content = { innerPadding -> - Box(Modifier.padding(innerPadding)) { - NavHost( - navController = navigator.navController, - startDestination = AccountScreen.SignIn.route - ) { - signInNavGraph( - coroutineScope = coroutineScope, - snackBarHostState = snackBarHostState, - navController = navigator.navController, - onBackClick = { navigator.popBackStack() }, - navigateToSignIn = { navigator.navigateSignIn() }, - navigateToSignInEmail = { navigator.navigateSignInEmail() }, - navigateToResetPassword = { navigator.navigateResetPassword() }, - navigateToSignUpEmail = { navigator.navigateSignUpEmail() }, - navigateToProfileSetting = { navigator.navigateProfileSetting() }, - navigateToRules = { route -> navigator.navigateRules(route) }, - bottomSheetState = bottomSheetState, - bottomSheetContent = bottomSheetContent - ) + val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) + + BottomSheetScaffold( + modifier = Modifier.systemBarsPadding(), + scaffoldState = bottomSheetState, + sheetDragHandle = null, + sheetContent = { + Box(modifier = Modifier.navigationBarsPadding()) { + bottomSheetContent?.invoke() + } + }, + sheetPeekHeight = 0.dp, + snackbarHost = { + SnackbarHost( + hostState = snackBarHostState, + modifier = Modifier.navigationBarsPadding() + ) + }, + content = { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavHost( + navController = navigator.navController, + startDestination = AccountScreen.SignIn.route + ) { + signInNavGraph( + coroutineScope = coroutineScope, + snackBarHostState = snackBarHostState, + navController = navigator.navController, + onBackClick = { navigator.popBackStack() }, + navigateToSignIn = { navigator.navigateSignIn() }, + navigateToSignInEmail = { navigator.navigateSignInEmail() }, + navigateToResetPassword = { navigator.navigateResetPassword() }, + navigateToSignUpEmail = { navigator.navigateSignUpEmail() }, + navigateToProfileSetting = { navigator.navigateProfileSetting() }, + navigateToRules = { route -> navigator.navigateRules(route) }, + bottomSheetState = bottomSheetState, + bottomSheetContent = { content -> + bottomSheetContent = content } - } - }) + ) + } + + if (animatedDimAlpha > 0f) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Dark.copy(alpha = animatedDimAlpha)) + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { + coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + } + ) + } + } } - } + ) } sealed class AccountScreen(val route: String) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInEmailScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInEmailScreen.kt index e2039c6d..4511ae24 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInEmailScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInEmailScreen.kt @@ -137,51 +137,46 @@ fun SignInEmailScreen( emailState.value.isNotBlank() && passwordState.value.isNotBlank() } - Surface( + Column( modifier = Modifier - .background(White_FFFFFF) .fillMaxSize() + .background(DayoTheme.colorScheme.background), + verticalArrangement = Arrangement.Top ) { + SignInEmailActionbarLayout(onBackClick = onBackClick) Column( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 0.dp) + .fillMaxWidth() + .wrapContentSize(), verticalArrangement = Arrangement.Top ) { - SignInEmailActionbarLayout(onBackClick = onBackClick) - Column( + Spacer( modifier = Modifier - .background(White_FFFFFF) - .padding(horizontal = 20.dp, vertical = 0.dp) .fillMaxWidth() - .wrapContentSize(), - verticalArrangement = Arrangement.Top - ) { - Spacer( - modifier = Modifier - .fillMaxWidth() - .height(8.dp) - ) - SignInEmailTitle() - Spacer( - modifier = Modifier - .fillMaxWidth() - .height(20.dp) - ) - SignInEmailInputLayout( - emailValue = emailState.value, - onEmailChange = { emailState.value = it }, - passwordValue = passwordState.value, - onPasswordChange = { passwordState.value = it }, - onForgetPasswordClick = onForgetPasswordClick, - onSignInClick = { onSignInClick(emailState.value, passwordState.value) } - ) - } - Spacer(modifier = Modifier.weight(1f)) - SignInEmailBottomLayout( - onSignUpClick = onSignUpClick, - onSignInClick = { onSignInClick(emailState.value, passwordState.value) }, - isSignInButtonEnabled = isSignInButtonEnabled + .height(8.dp) + ) + SignInEmailTitle() + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(20.dp) + ) + SignInEmailInputLayout( + emailValue = emailState.value, + onEmailChange = { emailState.value = it }, + passwordValue = passwordState.value, + onPasswordChange = { passwordState.value = it }, + onForgetPasswordClick = onForgetPasswordClick, + onSignInClick = { onSignInClick(emailState.value, passwordState.value) } ) } + Spacer(modifier = Modifier.weight(1f)) + SignInEmailBottomLayout( + onSignUpClick = onSignUpClick, + onSignInClick = { onSignInClick(emailState.value, passwordState.value) }, + isSignInButtonEnabled = isSignInButtonEnabled + ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt index 500a08d4..56b09ef1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignInNavigation.kt @@ -96,6 +96,8 @@ fun NavGraphBuilder.signInNavGraph( SignUpEmailRoute( coroutineScope = coroutineScope, snackBarHostState = snackBarHostState, + bottomSheetState = bottomSheetState, + bottomSheetContent = bottomSheetContent, onBackClick = onBackClick, accountViewModel = hiltViewModel(parentStackEntry), profileSettingViewModel = hiltViewModel(parentStackEntry), @@ -109,6 +111,8 @@ fun NavGraphBuilder.signInNavGraph( SignUpEmailRoute( coroutineScope = coroutineScope, snackBarHostState = snackBarHostState, + bottomSheetState = bottomSheetState, + bottomSheetContent = bottomSheetContent, onBackClick = onBackClick, accountViewModel = hiltViewModel(parentStackEntry), profileSettingViewModel = hiltViewModel(parentStackEntry), diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt index 4746159c..3ddb414b 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/account/SignUpEmailScreen.kt @@ -81,6 +81,8 @@ const val IMAGE_TEMP_FILE_EXTENSION = ".jpg" internal fun SignUpEmailRoute( coroutineScope: CoroutineScope = rememberCoroutineScope(), snackBarHostState: SnackbarHostState, + bottomSheetState: BottomSheetScaffoldState, + bottomSheetContent: (@Composable () -> Unit) -> Unit, onBackClick: () -> Unit = {}, accountViewModel: AccountViewModel = hiltViewModel(), profileSettingViewModel: ProfileSettingViewModel = hiltViewModel(), @@ -88,7 +90,6 @@ internal fun SignUpEmailRoute( ) { val context = LocalContext.current val contentResolver = context.contentResolver - val bottomSheetState = getBottomSheetDialogState() val keyboardController = LocalSoftwareKeyboardController.current val bitmapOptions = BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.ARGB_8888 } @@ -224,27 +225,29 @@ internal fun SignUpEmailRoute( message = stringResource(R.string.signup_email_alert_message_loading) ) - ProfileImageBottomSheetDialog( - bottomSheetState, - onClickProfileSelect = { - coroutineScope.launch { - showProfileGallery = true - bottomSheetState.bottomSheetState.hide() - } - }, - onClickProfileCapture = { - coroutineScope.launch { - showProfileCapture = true - bottomSheetState.bottomSheetState.hide() - } - }, - onClickProfileReset = { - profileImgState.value = null - coroutineScope.launch { - bottomSheetState.bottomSheetState.hide() - } - }, - ) + bottomSheetContent { + ProfileImageBottomSheetDialog( + bottomSheetState = bottomSheetState, + onClickProfileSelect = { + coroutineScope.launch { + showProfileGallery = true + bottomSheetState.bottomSheetState.hide() + } + }, + onClickProfileCapture = { + coroutineScope.launch { + showProfileCapture = true + bottomSheetState.bottomSheetState.hide() + } + }, + onClickProfileReset = { + profileImgState.value = null + coroutineScope.launch { + bottomSheetState.bottomSheetState.hide() + } + }, + ) + } } @Composable diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt index 1ae449bb..ae737c46 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt @@ -82,6 +82,7 @@ import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.dialog.ConfirmDialog import daily.dayo.presentation.viewmodel.FolderUiState import daily.dayo.presentation.viewmodel.FolderViewModel +import daily.dayo.presentation.viewmodel.ProfileViewModel import kotlinx.coroutines.flow.flowOf @Composable @@ -92,10 +93,12 @@ fun FolderScreen( onPostMoveClick: () -> Unit, onWritePostWithFolderClick: () -> Unit, onBackClick: () -> Unit, - folderViewModel: FolderViewModel = hiltViewModel() + folderViewModel: FolderViewModel = hiltViewModel(), + profileViewModel: ProfileViewModel = hiltViewModel() ) { val folderUiState by folderViewModel.uiState.collectAsStateWithLifecycle() val folderPosts = folderUiState.folderPosts.collectAsLazyPagingItems() + val isMine = folderUiState.folderInfo.memberId == profileViewModel.currentMemberId val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current @@ -155,6 +158,7 @@ fun FolderScreen( } FolderScreen( + isMine = isMine, folderUiState = folderUiState, folderPosts = folderPosts, optionMenu = optionMenu, @@ -197,6 +201,7 @@ fun FolderScreen( @Composable private fun FolderScreen( + isMine: Boolean, folderUiState: FolderUiState, folderPosts: LazyPagingItems, optionMenu: List, @@ -243,20 +248,22 @@ private fun FolderScreen( } }, rightIcon = { - IconButton( - onClick = { optionExpanded.value = optionExpanded.value.not() } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_option_horizontal), - contentDescription = stringResource(id = R.string.folder_option), - tint = Dark + if (isMine) { + IconButton( + onClick = { optionExpanded.value = optionExpanded.value.not() } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_option_horizontal), + contentDescription = stringResource(id = R.string.folder_option), + tint = Dark + ) + } + + FolderDropdownMenu( + menuItems = optionMenu, + expanded = optionExpanded ) } - - FolderDropdownMenu( - menuItems = optionMenu, - expanded = optionExpanded - ) } ) } @@ -297,7 +304,7 @@ private fun FolderScreen( ) { innerPadding -> Column(modifier = Modifier.padding(innerPadding)) { if (!isEditMode) { - FolderInformation(folderInfo, onWritePostWithFolderClick) + FolderInformation(isMine, folderInfo, onWritePostWithFolderClick) } FolderHeader( postCount = folderInfo.postCount, @@ -318,7 +325,11 @@ private fun FolderScreen( } @Composable -private fun FolderInformation(folderInfo: FolderInfo, onWritePostWithFolderClick: () -> Unit) { +private fun FolderInformation( + isMine: Boolean, + folderInfo: FolderInfo, + onWritePostWithFolderClick: () -> Unit +) { Column( modifier = Modifier .fillMaxWidth() @@ -357,32 +368,34 @@ private fun FolderInformation(folderInfo: FolderInfo, onWritePostWithFolderClick style = DayoTheme.typography.b6 ) - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = onWritePostWithFolderClick, - modifier = Modifier - .fillMaxWidth() - .height(40.dp), - shape = RoundedCornerShape(12.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Gray6_F0F1F3, - contentColor = Gray2_767B83 - ), - contentPadding = PaddingValues(vertical = 9.5.dp) - ) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = stringResource(id = R.string.folder_post_add_icon_description) - ) + if (isMine) { + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = onWritePostWithFolderClick, + modifier = Modifier + .fillMaxWidth() + .height(40.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Gray6_F0F1F3, + contentColor = Gray2_767B83 + ), + contentPadding = PaddingValues(vertical = 9.5.dp) + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = stringResource(id = R.string.folder_post_add_icon_description) + ) - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(4.dp)) - Text( - text = stringResource(id = R.string.folder_post_add), - color = Gray2_767B83, - style = DayoTheme.typography.b5 - ) + Text( + text = stringResource(id = R.string.folder_post_add), + color = Gray2_767B83, + style = DayoTheme.typography.b5 + ) + } } } } @@ -477,7 +490,8 @@ private fun FolderSortSelector(folderOrder: FolderOrder, onClickSort: () -> Unit } Row( - modifier = Modifier.clickableSingle { onClickSort() } + modifier = Modifier.clickableSingle { onClickSort() }, + verticalAlignment = Alignment.CenterVertically ) { Icon( painter = painterResource(id = R.drawable.ic_sort), @@ -632,8 +646,11 @@ private fun PreviewFolderScreen() { selectedPosts = emptySet() ) + val isMine = false + DayoTheme { FolderScreen( + isMine = isMine, folderUiState = folderUiState, folderPosts = folderUiState.folderPosts.collectAsLazyPagingItems(), optionMenu = listOf(), @@ -672,8 +689,11 @@ private fun PreviewFolderScreenEditMode() { selectedPosts = emptySet() ) + val isMine = true + DayoTheme { FolderScreen( + isMine = isMine, folderUiState = folderUiState, folderPosts = folderUiState.folderPosts.collectAsLazyPagingItems(), optionMenu = listOf(), diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainNavigator.kt b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainNavigator.kt index f74bd775..2177b76d 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainNavigator.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainNavigator.kt @@ -212,8 +212,13 @@ class MainNavigator( navController.navigatePostLikeUsers(postId = postId) } - fun navigateUp() { - navController.navigateUp() + fun navigateUp(): Boolean { + return if (navController.previousBackStackEntry != null) { + navController.navigateUp() + true + } else { + false + } } @Composable diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt index 49a43dc1..c9fc510a 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/main/MainScreen.kt @@ -1,5 +1,7 @@ package daily.dayo.presentation.screen.main +import android.widget.Toast +import androidx.activity.compose.BackHandler import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.ExperimentalSharedTransitionApi @@ -30,6 +32,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -37,6 +40,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview @@ -78,6 +82,7 @@ import kotlinx.coroutines.launch @Composable internal fun MainScreen( onAdRequest: (onRewardSuccess: () -> Unit) -> Unit, + onExit: () -> Unit, navigator: MainNavigator = rememberMainNavigator(), profileViewModel: ProfileViewModel = hiltViewModel() ) { @@ -92,6 +97,22 @@ internal fun MainScreen( derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } } val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) + val context = LocalContext.current + var lastBackPressedTime by remember { mutableLongStateOf(0L) } + val toast = remember { Toast.makeText(context, context.getString(R.string.main_finish_toast), Toast.LENGTH_SHORT) } + + BackHandler { + if (!navigator.navigateUp()) { // 뒤로갈 화면 없는 경우 + val currentTime = System.currentTimeMillis() + if (currentTime - lastBackPressedTime < 2000) { + toast?.cancel() + onExit() // 앱 종료 + } else { + lastBackPressedTime = currentTime + toast?.show() + } + } + } SharedTransitionLayout { BottomSheetScaffold( diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt index 5cdd68a4..790f1c49 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt @@ -11,30 +11,36 @@ import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.launch +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text +import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffoldState -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -195,7 +201,7 @@ internal fun MyPageEditScreen( ) } -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun MyPageEditScreen( profileInfo: MutableState, @@ -208,18 +214,18 @@ private fun MyPageEditScreen( onBackClick: () -> Unit, onConfirmClick: () -> Unit, ) { + val bottomSheetDimAlpha by remember { + derivedStateOf { if (bottomSheetState.bottomSheetState.currentValue == SheetValue.Expanded) 0.6f else 0f } + } + val animatedDimAlpha by animateFloatAsState(targetValue = bottomSheetDimAlpha) val scrollState = rememberScrollState() val coroutineScope = rememberCoroutineScope() - Scaffold( - topBar = { - MyPageEditTopNavigation( - confirmEnabled = nickNameErrorMessage.isEmpty(), - onBackClick = onBackClick, - onConfirmClick = onConfirmClick - ) - }, - bottomBar = { + + BottomSheetScaffold( + scaffoldState = bottomSheetState, + sheetDragHandle = null, + sheetContent = { ProfileImageBottomSheetDialog( bottomSheetState, onClickProfileSelect, @@ -227,76 +233,100 @@ private fun MyPageEditScreen( onClickProfileReset ) }, - content = { contentPadding -> - Column( - modifier = Modifier - .background(DayoTheme.colorScheme.background) - .fillMaxSize() - .verticalScroll(scrollState) - .padding(contentPadding) - .padding(vertical = 32.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val placeholderResId = remember { R.drawable.ic_profile_default_user_profile } - BadgeRoundImageView( - context = LocalContext.current, - imageUrl = modifiedProfileImage, - imageDescription = "my page profile image", - placeholderResId = placeholderResId, - contentModifier = Modifier - .size(100.dp) - .aspectRatio(1f) - .clip(RoundedCornerShape(percent = 50)) - .clickableSingle( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { - coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } - } - ) - ) - - Spacer(modifier = Modifier.height(36.dp)) - - DayoTextField( - value = profileInfo.value?.nickname ?: "", - onValueChange = { textValue -> - profileInfo.value = profileInfo.value?.copy( - nickname = textValue + content = { + Box { + Scaffold( + modifier = Modifier.navigationBarsPadding(), + topBar = { + MyPageEditTopNavigation( + confirmEnabled = nickNameErrorMessage.isEmpty(), + onBackClick = onBackClick, + onConfirmClick = onConfirmClick ) - }, - label = stringResource(id = R.string.nickname), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 18.dp), - isError = nickNameErrorMessage.isNotEmpty(), - errorMessage = nickNameErrorMessage - ) - - Spacer(modifier = Modifier.height(32.dp)) - - Column(modifier = Modifier.padding(horizontal = 18.dp)) { - Text( - text = stringResource(id = R.string.email), - style = DayoTheme.typography.caption3.copy( - color = Gray4_C5CAD2, - fontWeight = FontWeight.SemiBold + } + ) { contentPadding -> + + Column( + modifier = Modifier + .background(DayoTheme.colorScheme.background) + .fillMaxSize() + .verticalScroll(scrollState) + .padding(contentPadding) + .padding(vertical = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val placeholderResId = remember { R.drawable.ic_profile_default_user_profile } + BadgeRoundImageView( + context = LocalContext.current, + imageUrl = modifiedProfileImage, + imageDescription = "my page profile image", + placeholderResId = placeholderResId, + contentModifier = Modifier + .size(100.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(percent = 50)) + .clickableSingle( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { + coroutineScope.launch { bottomSheetState.bottomSheetState.expand() } + } + ) ) - ) - Text( - text = profileInfo.value?.email ?: "", - modifier = Modifier.padding(vertical = 8.dp), - style = DayoTheme.typography.b4.copy( - color = Gray2_767B83, - fontWeight = FontWeight.SemiBold + Spacer(modifier = Modifier.height(36.dp)) + + DayoTextField( + value = profileInfo.value?.nickname ?: "", + onValueChange = { textValue -> + profileInfo.value = profileInfo.value?.copy( + nickname = textValue + ) + }, + label = stringResource(id = R.string.nickname), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp), + isError = nickNameErrorMessage.isNotEmpty(), + errorMessage = nickNameErrorMessage ) - ) - Divider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = Gray6_F0F1F3 + Spacer(modifier = Modifier.height(32.dp)) + + Column(modifier = Modifier.padding(horizontal = 18.dp)) { + Text( + text = stringResource(id = R.string.email), + style = DayoTheme.typography.caption3.copy( + color = Gray4_C5CAD2, + fontWeight = FontWeight.SemiBold + ) + ) + + Text( + text = profileInfo.value?.email ?: "", + modifier = Modifier.padding(vertical = 8.dp), + style = DayoTheme.typography.b4.copy( + color = Gray2_767B83, + fontWeight = FontWeight.SemiBold + ) + ) + + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = Gray6_F0F1F3 + ) + } + } + } + if (animatedDimAlpha > 0f) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Dark.copy(alpha = animatedDimAlpha)) + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { + coroutineScope.launch { bottomSheetState.bottomSheetState.hide() } + } ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt index d6ab802a..aa64311f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt @@ -20,18 +20,18 @@ 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.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.compose.collectAsStateWithLifecycle import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey +import daily.dayo.domain.model.SearchOrder import daily.dayo.presentation.BuildConfig import daily.dayo.presentation.R import daily.dayo.presentation.common.extension.clickableSingle @@ -51,13 +51,13 @@ fun SearchPostHashtagScreen( onPostClick: (Long) -> Unit, searchViewModel: SearchViewModel = hiltViewModel() ) { - val isLatest by rememberSaveable { mutableStateOf(true) } // TODO api 수정 후 구현 + val searchHashtagOrder by searchViewModel.searchHashtagOrder.collectAsStateWithLifecycle() val hashtagPosts = searchViewModel.searchTagList.collectAsLazyPagingItems() val hashtagPostsCount by searchViewModel.searchTagTotalCount.collectAsStateWithLifecycle(0) LaunchedEffect(Unit) { with(searchViewModel) { - searchHashtag(hashtag = hashtag) + searchHashtag(hashtag = hashtag, searchOrder = searchHashtagOrder) } } @@ -94,7 +94,11 @@ fun SearchPostHashtagScreen( ) { // description item(span = { GridItemSpan(2) }) { - SearchResultDescription(hashtagPostsCount, isLatest) + SearchResultDescription( + resultCount = hashtagPostsCount, + searchOrder = searchHashtagOrder, + onClickSort = { searchViewModel.toggleSearchHashtagOrder(hashtag) } + ) } // posts @@ -125,7 +129,16 @@ fun SearchPostHashtagScreen( } @Composable -private fun SearchResultDescription(resultCount: Int, isLatest: Boolean) { +private fun SearchResultDescription( + resultCount: Int, + searchOrder: SearchOrder, + onClickSort: () -> Unit +) { + val sortResId = when (searchOrder) { + SearchOrder.NEW -> R.string.search_hashtag_sort_newest + SearchOrder.OLD -> R.string.search_hashtag_sort_oldest + } + Row(modifier = Modifier.fillMaxWidth()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -149,16 +162,16 @@ private fun SearchResultDescription(resultCount: Int, isLatest: Boolean) { horizontalArrangement = Arrangement.End, modifier = Modifier .padding(vertical = 12.dp) - .weight(1f) + .clickableSingle { onClickSort() } ) { Icon( painter = painterResource(id = R.drawable.ic_swap_vertical), - contentDescription = if (isLatest) "최신순" else "오래된순", + contentDescription = stringResource(id = sortResId), tint = Gray1_50545B ) Text( style = DayoTheme.typography.caption1.copy(color = Gray2_767B83), - text = if (isLatest) "최신순" else "오래된순" + text = stringResource(id = sortResId) ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt index 70ad81a5..6dc82102 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn @@ -30,6 +31,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Divider +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.TabRow @@ -204,21 +206,24 @@ fun SearchResultScreen( val coroutineScope = rememberCoroutineScope() val pagerState = rememberPagerState { 2 } - Surface( - color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier.fillMaxSize() - ) { - Column( - modifier = Modifier.fillMaxSize() - ) { + Scaffold( + topBar = { SearchActionbarLayout( + modifier = Modifier.statusBarsPadding(), initialKeyword = searchKeyword, onBackClick = onBackClick, onSearchClick = { keyword -> onSearchClick(keyword) } ) - + }, + containerColor = DayoTheme.colorScheme.background + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { TabRow( selectedTabIndex = pagerState.currentPage, containerColor = White, @@ -415,6 +420,7 @@ fun SearchResultTagView( imageDescription = "searched Image", modifier = Modifier .matchParentSize() + .aspectRatio(1f) .clickableSingle( interactionSource = imageInteractionSource, indication = null, diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt index 8be57468..0d78b2dd 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt @@ -4,7 +4,6 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -17,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn @@ -25,8 +25,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -119,36 +120,36 @@ fun SearchScreen( onKeywordDeleteClick: (String, SearchHistoryType) -> Unit, onHistoryClearClick: () -> Unit ) { - Surface( - color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - ) { - Column { + Scaffold( + topBar = { SearchActionbarLayout( + modifier = Modifier.statusBarsPadding(), onBackClick = onBackClick, onSearchClick = onSearchClick ) - SetSearchHistoryLayout( - onKeywordClick = onSearchClick, - onKeywordDeleteClick = onKeywordDeleteClick, - onHistoryClearClick = onHistoryClearClick, - searchHistory = searchHistory - ) - } + }, + containerColor = DayoTheme.colorScheme.background + ) { innerPadding -> + SetSearchHistoryLayout( + modifier = Modifier.padding(innerPadding), + onKeywordClick = onSearchClick, + onKeywordDeleteClick = onKeywordDeleteClick, + onHistoryClearClick = onHistoryClearClick, + searchHistory = searchHistory + ) } } @Composable fun SearchActionbarLayout( initialKeyword: String = "", + modifier: Modifier, onBackClick: () -> Unit, onSearchClick: (String) -> Unit ) { Surface( color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier + modifier = modifier .height(IntrinsicSize.Min) .fillMaxWidth() ) { @@ -296,13 +297,14 @@ private fun SetClearSearchHistoryLayout(onHistoryClearClick: () -> Unit) { @Composable private fun SetSearchHistoryLayout( + modifier: Modifier, onKeywordClick: (String) -> Unit, onKeywordDeleteClick: (String, SearchHistoryType) -> Unit, onHistoryClearClick: () -> Unit, searchHistory: SearchHistory ) { LazyColumn( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(DayoTheme.colorScheme.background) ) { diff --git a/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt b/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt index d52aad62..dd4cecd9 100644 --- a/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt +++ b/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import daily.dayo.domain.usecase.search.ClearSearchKeywordRecentUseCase import daily.dayo.domain.usecase.search.DeleteSearchKeywordRecentUseCase @@ -37,6 +38,9 @@ class SearchViewModel @Inject constructor( var searchKeyword = "" + private val _searchHashtagOrder = MutableStateFlow(SearchOrder.NEW) + val searchHashtagOrder get() = _searchHashtagOrder + private val _searchTagTotalCount = MutableStateFlow(0) val searchTagTotalCount get() = _searchTagTotalCount @@ -59,7 +63,11 @@ class SearchViewModel @Inject constructor( _searchHistory.emit(it) } - fun searchKeyword(keyword: String, keywordType: SearchHistoryType = SearchHistoryType.TAG) = + fun searchKeyword( + keyword: String, + keywordType: SearchHistoryType = SearchHistoryType.TAG, + searchOrder: SearchOrder = SearchOrder.NEW + ) { viewModelScope.launch { updateSearchKeywordRecentUseCase(keyword, keywordType) when (keywordType) { @@ -67,7 +75,7 @@ class SearchViewModel @Inject constructor( requestSearchTotalCountUseCase(keyword, SearchHistoryType.TAG).let { _searchTagTotalCount.emit(it) } - requestSearchTagUseCase(tag = keyword) + requestSearchTagUseCase(tag = keyword, searchOrder = searchOrder) .cachedIn(viewModelScope) .collectLatest { _searchTagList.emit(it) @@ -93,6 +101,7 @@ class SearchViewModel @Inject constructor( } } } + } suspend fun searchFollowUser(keyword: String) { requestSearchFollowUserUseCase(nickname = keyword) @@ -114,17 +123,26 @@ class SearchViewModel @Inject constructor( } } - fun searchHashtag(hashtag: String) { + fun searchHashtag(hashtag: String, searchOrder: SearchOrder) { viewModelScope.launch { - requestSearchTotalCountUseCase(tag = hashtag, SearchHistoryType.TAG).let { + requestSearchTotalCountUseCase(tag = hashtag, searchHistoryType = SearchHistoryType.TAG).let { _searchTagTotalCount.emit(it) } - requestSearchTagUseCase(tag = hashtag) + requestSearchTagUseCase(tag = hashtag, searchOrder = searchOrder) .cachedIn(viewModelScope) .collectLatest { _searchTagList.emit(it) } } } + + fun toggleSearchHashtagOrder(hashtag: String) { + val newOrder = when (_searchHashtagOrder.value) { + SearchOrder.NEW -> SearchOrder.OLD + SearchOrder.OLD -> SearchOrder.NEW + } + _searchHashtagOrder.value = newOrder + searchHashtag(hashtag, newOrder) + } } \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 59f0ed44..99efcf72 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -55,6 +55,9 @@ DENIED EXPLAINED + + 한 번 더 누르면 앱이 종료됩니다. + 인기가 많은 다꾸들을 구경할 수 있어요 내 취향의 다꾸러들을 팔로우하고 모아볼 수 있어요 @@ -366,6 +369,8 @@ 검색어를 입력해주세요. + 최신순 + 오래된순 최근 검색어 추천 검색어 앗! 찾으시는 검색 결과가 없어요.