diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeApiService.kt b/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeApiService.kt index 69fe5743..eddc631e 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeApiService.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeApiService.kt @@ -11,5 +11,5 @@ interface NoticeApiService { suspend fun requestAllNoticeList(@Query("end") end: Int): NetworkResponse @GET("/api/v1/notice/{noticeId}") - suspend fun requestDetailNotice(@Path("noticeId") noticeId: Int): NetworkResponse + suspend fun requestDetailNotice(@Path("noticeId") noticeId: Long): NetworkResponse } \ No newline at end of file diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeResponse.kt b/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeResponse.kt index 72173ea5..3c52dfe4 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeResponse.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/notice/NoticeResponse.kt @@ -18,7 +18,7 @@ data class NoticeDetailResponse( data class NoticeDto( @SerializedName("id") - val id: Int, + val id: Long, @SerializedName("title") val title: String, @SerializedName("createdDate") diff --git a/data/src/main/java/daily/dayo/data/repository/NoticeRepositoryImpl.kt b/data/src/main/java/daily/dayo/data/repository/NoticeRepositoryImpl.kt index 8832020a..8ed8b0bd 100644 --- a/data/src/main/java/daily/dayo/data/repository/NoticeRepositoryImpl.kt +++ b/data/src/main/java/daily/dayo/data/repository/NoticeRepositoryImpl.kt @@ -19,7 +19,7 @@ class NoticeRepositoryImpl @Inject constructor( NoticePagingSource(noticeApiService, NOTICE_PAGE_SIZE) }.flow - override suspend fun requestDetailNotice(noticeId: Int): NetworkResponse = + override suspend fun requestDetailNotice(noticeId: Long): NetworkResponse = when ( val response = noticeApiService.requestDetailNotice(noticeId)) { is NetworkResponse.Success -> NetworkResponse.Success(response.body?.toNoticeDetail()) diff --git a/domain/src/main/java/daily/dayo/domain/model/Notice.kt b/domain/src/main/java/daily/dayo/domain/model/Notice.kt index ee1cc21c..f70d015c 100644 --- a/domain/src/main/java/daily/dayo/domain/model/Notice.kt +++ b/domain/src/main/java/daily/dayo/domain/model/Notice.kt @@ -3,7 +3,7 @@ package daily.dayo.domain.model import java.io.Serializable data class Notice( - val noticeId: Int, + val noticeId: Long, val title: String, val uploadDate: String ) : Serializable diff --git a/domain/src/main/java/daily/dayo/domain/repository/NoticeRepository.kt b/domain/src/main/java/daily/dayo/domain/repository/NoticeRepository.kt index 04f85a62..537abffb 100644 --- a/domain/src/main/java/daily/dayo/domain/repository/NoticeRepository.kt +++ b/domain/src/main/java/daily/dayo/domain/repository/NoticeRepository.kt @@ -8,5 +8,5 @@ import kotlinx.coroutines.flow.Flow interface NoticeRepository { suspend fun requestAllNoticeList(): Flow> - suspend fun requestDetailNotice(noticeId: Int): NetworkResponse + suspend fun requestDetailNotice(noticeId: Long): NetworkResponse } \ No newline at end of file diff --git a/domain/src/main/java/daily/dayo/domain/usecase/notice/RequestDetailNoticeUseCase.kt b/domain/src/main/java/daily/dayo/domain/usecase/notice/RequestDetailNoticeUseCase.kt index 32bb03cc..b99db497 100644 --- a/domain/src/main/java/daily/dayo/domain/usecase/notice/RequestDetailNoticeUseCase.kt +++ b/domain/src/main/java/daily/dayo/domain/usecase/notice/RequestDetailNoticeUseCase.kt @@ -6,6 +6,6 @@ import javax.inject.Inject class RequestDetailNoticeUseCase @Inject constructor( private val noticeRepository: NoticeRepository ) { - suspend operator fun invoke(noticeId: Int) = + suspend operator fun invoke(noticeId: Long) = noticeRepository.requestDetailNotice(noticeId) } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/adapter/NoticeListAdapter.kt b/presentation/src/main/java/daily/dayo/presentation/adapter/NoticeListAdapter.kt deleted file mode 100644 index 90d3198d..00000000 --- a/presentation/src/main/java/daily/dayo/presentation/adapter/NoticeListAdapter.kt +++ /dev/null @@ -1,51 +0,0 @@ -package daily.dayo.presentation.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.navigation.Navigation -import androidx.paging.PagingDataAdapter -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import daily.dayo.domain.model.Notice -import daily.dayo.presentation.R -import daily.dayo.presentation.common.extension.navigateSafe -import daily.dayo.presentation.common.setOnDebounceClickListener -import daily.dayo.presentation.databinding.ItemNoticePostBinding -import daily.dayo.presentation.fragment.setting.notice.NoticeListFragmentDirections - -class NoticeListAdapter : - PagingDataAdapter(diffCallback) { - companion object { - private val diffCallback = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Notice, newItem: Notice): Boolean { - return oldItem.noticeId == newItem.noticeId - } - - override fun areContentsTheSame(oldItem: Notice, newItem: Notice): Boolean = - oldItem == newItem - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoticeListViewHolder = - NoticeListViewHolder( - ItemNoticePostBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) - - override fun onBindViewHolder(holder: NoticeListAdapter.NoticeListViewHolder, position: Int) { - getItem(position)?.let { holder.bind(it) } - } - - inner class NoticeListViewHolder(private val binding: ItemNoticePostBinding) : - RecyclerView.ViewHolder(binding.root) { - fun bind(notice: Notice) { - binding.notice = notice - binding.root.setOnDebounceClickListener { - Navigation.findNavController(it).navigateSafe( - currentDestinationId = R.id.NoticeListFragment, - action = R.id.action_noticeListFragment_to_noticeDetailFragment, - args = NoticeListFragmentDirections.actionNoticeListFragmentToNoticeDetailFragment(notice).arguments - ) - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/SettingFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/setting/SettingFragment.kt index 09466f99..0d57e8c3 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/SettingFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/setting/SettingFragment.kt @@ -88,10 +88,6 @@ class SettingFragment : Fragment() { private fun setNoticeButtonClickListener() { binding.layoutSettingNotice.setOnDebounceClickListener { - findNavController().navigateSafe( - currentDestinationId = R.id.SettingFragment, - action = R.id.action_settingFragment_to_noticeFragment - ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeDetailFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeDetailFragment.kt deleted file mode 100644 index 2635af5b..00000000 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeDetailFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -package daily.dayo.presentation.fragment.setting.notice - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.WebViewClient -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import daily.dayo.presentation.common.autoCleared -import daily.dayo.presentation.common.setOnDebounceClickListener -import daily.dayo.presentation.databinding.FragmentNoticeDetailBinding -import daily.dayo.presentation.viewmodel.NoticeViewModel -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class NoticeDetailFragment : Fragment() { - private var binding by autoCleared() - private val args by navArgs() - private val noticeViewModel by activityViewModels() - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FragmentNoticeDetailBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setBackButtonClickListener() - setNoticeDescription() - observeNoticeDetail() - getNoticeDetail() - } - - private fun setBackButtonClickListener() { - binding.btnNoticeDetailBack.setOnDebounceClickListener { - findNavController().navigateUp() - } - } - - private fun setNoticeDescription() { - binding.notice = args.notice - } - - private fun getNoticeDetail() { - noticeViewModel.requestDetailNotice(args.notice.noticeId) - } - - private fun observeNoticeDetail() { - noticeViewModel.detailNotice.observe(viewLifecycleOwner) { - with(binding.webviewNoticeDetailContents) { - apply { - webViewClient = WebViewClient() - settings.javaScriptEnabled = false - } - loadDataWithBaseURL(null, it, "text/html", "UTF-8", null) - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeListFragment.kt deleted file mode 100644 index 18ef378f..00000000 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/setting/notice/NoticeListFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -package daily.dayo.presentation.fragment.setting.notice - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.findNavController -import daily.dayo.presentation.R -import daily.dayo.presentation.common.CustomDividerDecoration -import daily.dayo.presentation.common.autoCleared -import daily.dayo.presentation.common.dp -import daily.dayo.presentation.common.setOnDebounceClickListener -import daily.dayo.presentation.databinding.FragmentNoticeListBinding -import daily.dayo.presentation.adapter.NoticeListAdapter -import daily.dayo.presentation.viewmodel.NoticeViewModel -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -@AndroidEntryPoint -class NoticeListFragment : Fragment() { - private var binding by autoCleared { onDestroyBindingView() } - private val noticeViewModel by activityViewModels() - private var noticeListAdapter: NoticeListAdapter? = null - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - binding = FragmentNoticeListBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setBackButtonClickListener() - getNoticeList() - initNoticeListAdapter() - } - - private fun onDestroyBindingView() { - noticeListAdapter = null - binding.rvNoticePost.adapter = null - } - - private fun setBackButtonClickListener() { - binding.btnNoticeBack.setOnDebounceClickListener { - findNavController().navigateUp() - } - } - - private fun initNoticeListAdapter() { - noticeListAdapter = NoticeListAdapter() - with(binding.rvNoticePost) { - adapter = noticeListAdapter - addItemDecoration( - CustomDividerDecoration( - 1.dp.toFloat(), - 18.dp.toFloat(), - ContextCompat.getColor(requireContext(), R.color.gray_6_F0F1F3) - ) - ) - } - - observeNoticeList() - } - - private fun getNoticeList() { - noticeViewModel.requestAllNoticeList() - } - - private fun observeNoticeList() { - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - noticeViewModel.noticeList.collectLatest { noticeList -> - noticeListAdapter?.submitData(noticeList) - } - } - } - } -} \ No newline at end of file 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 35119b39..c14420f3 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 @@ -18,6 +18,8 @@ import daily.dayo.presentation.screen.mypage.navigateFolderPostMove import daily.dayo.presentation.screen.mypage.navigateFollowMenu import daily.dayo.presentation.screen.mypage.navigateMyPage import daily.dayo.presentation.screen.mypage.navigateProfileEdit +import daily.dayo.presentation.screen.notice.navigateNotices +import daily.dayo.presentation.screen.notice.navigateNoticeDetail import daily.dayo.presentation.screen.post.navigatePost import daily.dayo.presentation.screen.post.navigatePostLikeUsers import daily.dayo.presentation.screen.profile.navigateProfile @@ -69,6 +71,14 @@ class MainNavigator( navController.navigateSearchPostHashtag(hashtag = hashtag) } + fun navigateNotices() { + navController.navigateNotices() + } + + fun navigateNoticeDetail(noticeId: Long) { + navController.navigateNoticeDetail(noticeId = noticeId) + } + fun navigateSettings() { navController.navigateSettings() } 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 5270e083..ee3cec13 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 @@ -3,6 +3,8 @@ package daily.dayo.presentation.screen.main import android.annotation.SuppressLint import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height @@ -41,6 +43,8 @@ import daily.dayo.presentation.screen.home.HomeRoute import daily.dayo.presentation.screen.home.homeNavGraph import daily.dayo.presentation.screen.mypage.MyPageRoute import daily.dayo.presentation.screen.mypage.myPageNavGraph +import daily.dayo.presentation.screen.mypage.navigateBackToFolder +import daily.dayo.presentation.screen.notice.noticeNavGraph import daily.dayo.presentation.screen.notification.NotificationRoute import daily.dayo.presentation.screen.notification.notificationNavGraph import daily.dayo.presentation.screen.post.postNavGraph @@ -54,9 +58,10 @@ import daily.dayo.presentation.theme.DayoTheme import daily.dayo.presentation.theme.Gray2_767B83 import daily.dayo.presentation.theme.White_FFFFFF import daily.dayo.presentation.view.dialog.getBottomSheetDialogState +import daily.dayo.presentation.viewmodel.NoticeViewModel import daily.dayo.presentation.viewmodel.ProfileViewModel -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalSharedTransitionApi::class) @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @Composable internal fun MainScreen( @@ -65,111 +70,184 @@ internal fun MainScreen( ) { val currentMemberId = profileViewModel.currentMemberId val coroutineScope = rememberCoroutineScope() + val noticeViewModel = hiltViewModel() val snackBarHostState = remember { SnackbarHostState() } val bottomSheetState = getBottomSheetDialogState() var bottomSheetContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) } - - Scaffold( - snackbarHost = { SnackbarHost(hostState = snackBarHostState) } - ) { + SharedTransitionLayout { Scaffold( - bottomBar = { bottomSheetContent?.invoke() } + snackbarHost = { SnackbarHost(hostState = snackBarHostState) } ) { Scaffold( - content = { innerPadding -> - Box(Modifier.padding(innerPadding)) { - NavHost( - navController = navigator.navController, - startDestination = Screen.Home.route - ) { - homeNavGraph( - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - onSearchClick = { navigator.navigateSearch() }, - coroutineScope = coroutineScope, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> bottomSheetContent = content }, - ) - feedNavGraph( - snackBarHostState = snackBarHostState, - onEmptyViewClick = { navigator.navigateHome() }, - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, - onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> bottomSheetContent = content } - ) - postNavGraph( - snackBarHostState = snackBarHostState, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, - onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, - onBackClick = { navigator.popBackStack() } - ) - searchNavGraph( - onBackClick = { navigator.popBackStack() }, - onSearch = { navigator.navigateSearchResult(it) }, - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - ) - writeNavGraph( - snackBarHostState = snackBarHostState, - navController = navigator.navController, - onBackClick = { navigator.navigateUp() }, - onTagClick = { navigator.navigateWriteTag() }, - onWriteFolderClick = { navigator.navigateWriteFolder() }, - onWriteFolderNewClick = { navigator.navigateWriteFolderNew() }, - bottomSheetState = bottomSheetState, - bottomSheetContent = { content -> bottomSheetContent = content }, - ) - myPageNavGraph( + bottomBar = { bottomSheetContent?.invoke() } + ) { + Scaffold( + content = { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavHost( navController = navigator.navController, - onBackClick = { navigator.popBackStack() }, - onSettingsClick = { navigator.navigateSettings() }, - onFollowButtonClick = { memberId, tabNum -> navigator.navigateFollowMenu(memberId, tabNum) }, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - onProfileEditClick = { navigator.navigateProfileEdit() }, - onBookmarkClick = { navigator.navigateBookmark() }, - onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, - onFolderCreateClick = { navigator.navigateFolderCreate() }, - onFolderEditClick = { folderId -> navigator.navigateFolderEdit(folderId) }, - onPostClick = { postId -> navigator.navigatePost(postId) }, - onPostMoveClick = { folderId -> navigator.navigateFolderPostMove(folderId) }, - navigateBackToFolder = { folderId -> navigator.navigateBackToFolder(folderId) } - ) - profileNavGraph( - snackBarHostState = snackBarHostState, - onFollowMenuClick = { memberId, tabNum -> navigator.navigateFollowMenu(memberId, tabNum) }, - onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, - onPostClick = { postId -> navigator.navigatePost(postId) }, - onBackClick = { navigator.popBackStack() } - ) - notificationNavGraph( - onPostClick = { navigator.navigatePost(it) }, - onProfileClick = { memberId -> navigator.navigateProfile(currentMemberId, memberId) }, - onNoticeClick = { /*TODO*/ } - ) - settingsNavGraph( - coroutineScope = coroutineScope, - snackBarHostState = snackBarHostState, - onProfileEditClick = { navigator.navigateProfileEdit() }, - onBlockUsersClick = { navigator.navigateBlockedUsers() }, - onPasswordChangeClick = { navigator.navigateChangePassword() }, - onSettingNotificationClick = { navigator.navigateSettingsNotification() }, - onBackClick = { navigator.popBackStack() } - ) + startDestination = Screen.Home.route + ) { + homeNavGraph( + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onSearchClick = { navigator.navigateSearch() }, + coroutineScope = coroutineScope, + bottomSheetState = bottomSheetState, + bottomSheetContent = { content -> + bottomSheetContent = content + }, + ) + feedNavGraph( + snackBarHostState = snackBarHostState, + onEmptyViewClick = { navigator.navigateHome() }, + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, + onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, + bottomSheetState = bottomSheetState, + bottomSheetContent = { content -> bottomSheetContent = content } + ) + postNavGraph( + snackBarHostState = snackBarHostState, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onPostLikeUsersClick = { navigator.navigatePostLikeUsers(it) }, + onPostHashtagClick = { navigator.navigateSearchPostHashtag(it) }, + onBackClick = { navigator.popBackStack() } + ) + searchNavGraph( + onBackClick = { navigator.popBackStack() }, + onSearch = { navigator.navigateSearchResult(it) }, + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + ) + writeNavGraph( + snackBarHostState = snackBarHostState, + navController = navigator.navController, + onBackClick = { navigator.navigateUp() }, + onTagClick = { navigator.navigateWriteTag() }, + onWriteFolderClick = { navigator.navigateWriteFolder() }, + onWriteFolderNewClick = { navigator.navigateWriteFolderNew() }, + bottomSheetState = bottomSheetState, + bottomSheetContent = { content -> + bottomSheetContent = content + }, + ) + myPageNavGraph( + navController = navigator.navController, + onBackClick = { navigator.popBackStack() }, + onSettingsClick = { navigator.navigateSettings() }, + onFollowButtonClick = { memberId, tabNum -> + navigator.navigateFollowMenu( + memberId, + tabNum + ) + }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onProfileEditClick = { navigator.navigateProfileEdit() }, + onBookmarkClick = { navigator.navigateBookmark() }, + onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, + onFolderCreateClick = { navigator.navigateFolderCreate() }, + onFolderEditClick = { folderId -> + navigator.navigateFolderEdit( + folderId + ) + }, + onPostClick = { postId -> navigator.navigatePost(postId) }, + onPostMoveClick = { folderId -> + navigator.navigateFolderPostMove( + folderId + ) + }, + navigateBackToFolder = { folderId -> + navigator.navigateBackToFolder( + folderId + ) + } + ) + profileNavGraph( + snackBarHostState = snackBarHostState, + onFollowMenuClick = { memberId, tabNum -> + navigator.navigateFollowMenu( + memberId, + tabNum + ) + }, + onFolderClick = { folderId -> navigator.navigateFolder(folderId) }, + onPostClick = { postId -> navigator.navigatePost(postId) }, + onBackClick = { navigator.popBackStack() } + ) + notificationNavGraph( + onPostClick = { navigator.navigatePost(it) }, + onProfileClick = { memberId -> + navigator.navigateProfile( + currentMemberId, + memberId + ) + }, + onNoticeClick = { noticeId -> + navigator.navigateNoticeDetail( + noticeId + ) + }, + ) + settingsNavGraph( + coroutineScope = coroutineScope, + snackBarHostState = snackBarHostState, + onProfileEditClick = { navigator.navigateProfileEdit() }, + onBlockUsersClick = { navigator.navigateBlockedUsers() }, + onPasswordChangeClick = { navigator.navigateChangePassword() }, + onSettingNotificationClick = { navigator.navigateSettingsNotification() }, + onNoticesClick = { navigator.navigateNotices() }, + onBackClick = { navigator.popBackStack() } + ) + noticeNavGraph( + noticeViewModel = noticeViewModel, + onBackClick = { navigator.popBackStack() }, + onNoticeDetailClick = { noticeId -> + navigator.navigateNoticeDetail( + noticeId + ) + }, + sharedTransitionScope = this@SharedTransitionLayout, + ) + } } + }, + bottomBar = { + MainBottomNavigation( + visible = navigator.shouldShowBottomBar(), + navController = navigator.navController + ) } - }, - bottomBar = { - MainBottomNavigation( - visible = navigator.shouldShowBottomBar(), - navController = navigator.navController - ) - } - ) + ) + } } } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeDetailScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeDetailScreen.kt new file mode 100644 index 00000000..d838117a --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeDetailScreen.kt @@ -0,0 +1,182 @@ +package daily.dayo.presentation.screen.notice + +import android.webkit.WebView +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +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.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Divider +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.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import daily.dayo.presentation.R +import daily.dayo.presentation.theme.Dark +import daily.dayo.presentation.theme.DayoTheme +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.Gray6_F0F1F3 +import daily.dayo.presentation.view.NoRippleIconButton +import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.viewmodel.NoticeViewModel + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun NoticeDetailScreen( + noticeId: Long, + onBackClick: () -> Unit = {}, + noticeViewModel: NoticeViewModel = hiltViewModel(), + animatedVisibilityScope: AnimatedVisibilityScope, + sharedElementScope: SharedTransitionScope, +) { + val scrollState = rememberScrollState() + val noticeDetail by noticeViewModel.detailNotice.collectAsStateWithLifecycle() + val notice = noticeViewModel.selectedNotice.collectAsStateWithLifecycle().value + + LaunchedEffect(Unit) { + noticeViewModel.requestDetailNotice(noticeId) + } + + Scaffold( + topBar = { + NoticeDetailActionbarLayout(onBackClick = onBackClick) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .background(DayoTheme.colorScheme.background) + .padding(innerPadding) + .fillMaxSize() + .verticalScroll(scrollState), + ) { + NoticeDetail( + noticeId = noticeId, + title = notice?.title ?: "공지사항", + contents = noticeDetail?.data ?: "", + uploadDate = notice?.uploadDate ?: "0000.00.00", + sharedTransitionScope = sharedElementScope, + animatedVisibilityScope = animatedVisibilityScope, + ) + } + } +} + +@Preview +@Composable +fun NoticeDetailActionbarLayout( + onBackClick: () -> Unit = {}, +) { + TopNavigation( + leftIcon = { + NoRippleIconButton( + onClick = { onBackClick() }, + iconContentDescription = stringResource(R.string.back_sign), + iconPainter = painterResource(id = R.drawable.ic_arrow_left), + ) + }, + ) +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun NoticeDetail( + noticeId: Long, + title: String = "공지사항", + contents: String = "공지사항 내용", + uploadDate: String = "0000.00.00", + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, +) { + with(sharedTransitionScope) { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 20.dp, end = 20.dp, top = 4.dp) + .background(DayoTheme.colorScheme.background) + ) { + NoticeDetailHeader( + titleModifier = Modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = "title_$noticeId"), + animatedVisibilityScope = animatedVisibilityScope, + ), + uploadDateModifier = Modifier.sharedBounds( + sharedContentState = rememberSharedContentState(key = "uploadDate_$noticeId"), + animatedVisibilityScope = animatedVisibilityScope, + ), + title = title, + uploadDate = uploadDate, + ) + Divider( + modifier = Modifier.padding(top = 20.dp, bottom = 18.dp), + color = Gray6_F0F1F3, + thickness = 1.dp, + ) + + AndroidView( + factory = { context -> + WebView(context).apply { + settings.javaScriptEnabled = true + loadDataWithBaseURL(null, contents, "text/html", "UTF-8", null) + } + }, + update = { webView -> + webView.loadDataWithBaseURL(null, contents, "text/html", "UTF-8", null) + }, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + ) + } + } +} + +@Composable +fun NoticeDetailHeader( + titleModifier: Modifier, + uploadDateModifier: Modifier, + title: String = "공지사항", + uploadDate: String = "0000.00.00", +) { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + ) { + Text( + modifier = uploadDateModifier, + text = uploadDate, + style = DayoTheme.typography.caption4, + color = Gray3_9FA5AE, + softWrap = false, + overflow = TextOverflow.Visible + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + modifier = titleModifier, + text = title, + style = DayoTheme.typography.b1, + color = Dark, + softWrap = false, + overflow = TextOverflow.Visible + ) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeNavigation.kt new file mode 100644 index 00000000..b0784d9b --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticeNavigation.kt @@ -0,0 +1,65 @@ +package daily.dayo.presentation.screen.notice + +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.material3.SnackbarHostState +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import daily.dayo.presentation.viewmodel.NoticeViewModel +import kotlinx.coroutines.CoroutineScope + +fun NavController.navigateNotices() { + navigate(NoticeRoute.route) +} + +fun NavController.navigateNoticeDetail(noticeId: Long) { + navigate(NoticeRoute.noticeDetail(noticeId)) +} + +@OptIn(ExperimentalSharedTransitionApi::class) +fun NavGraphBuilder.noticeNavGraph( + noticeViewModel: NoticeViewModel, + onBackClick: () -> Unit, + onNoticeDetailClick: (Long) -> Unit, + sharedTransitionScope: SharedTransitionScope, +) { + composable(NoticeRoute.route) { + NoticesScreen( + onBackClick = onBackClick, + onNoticeDetailClick = onNoticeDetailClick, + noticeViewModel = noticeViewModel, + sharedElementScope = sharedTransitionScope, + animatedVisibilityScope = this, + ) + } + + composable( + route = NoticeRoute.noticeDetailRoute, + arguments = listOf( + navArgument("noticeId") { + type = NavType.LongType + } + ) + ) { navBackStackEntry -> + navBackStackEntry.arguments?.getLong("noticeId")?.let { noticeId -> + NoticeDetailScreen( + noticeId = noticeId, + onBackClick = onBackClick, + noticeViewModel = noticeViewModel, + sharedElementScope = sharedTransitionScope, + animatedVisibilityScope = this, + ) + } + } +} + +object NoticeRoute { + const val route = "notice" + + const val noticeDetailRoute = "$route/{noticeId}" + + fun noticeDetail(noticeId: Long) = "$route/$noticeId" +} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticesScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticesScreen.kt new file mode 100644 index 00000000..c103a4fc --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/notice/NoticesScreen.kt @@ -0,0 +1,155 @@ +package daily.dayo.presentation.screen.notice + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +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.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +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 androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemKey +import daily.dayo.domain.model.Notice +import daily.dayo.presentation.R +import daily.dayo.presentation.theme.Dark +import daily.dayo.presentation.theme.DayoTheme +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.view.NoRippleIconButton +import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.viewmodel.NoticeViewModel + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun NoticesScreen( + sharedElementScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope, + onBackClick: () -> Unit = {}, + onNoticeDetailClick: (Long) -> Unit = {}, + noticeViewModel: NoticeViewModel = hiltViewModel(), +) { + val notices = noticeViewModel.noticeList.collectAsLazyPagingItems() + + LaunchedEffect(Unit) { + noticeViewModel.requestAllNoticeList() + } + + Scaffold( + topBar = { + NoticesActionbarLayout(onBackClick = onBackClick) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .background(DayoTheme.colorScheme.background) + .padding(innerPadding) + .fillMaxSize(), + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(vertical = 8.dp), + ) { + items( + count = notices.itemCount, + key = notices.itemKey() + ) { index -> + val notice = notices[index] + notice?.let { + Notice( + notice = it, + onNoticeDetailClick = { + noticeViewModel.selectNotice(notice) + onNoticeDetailClick(notice.noticeId) + }, + sharedElementScope = sharedElementScope, + animatedVisibilityScope = animatedVisibilityScope, + ) + } + } + } + } + } +} + +@Preview +@Composable +fun NoticesActionbarLayout( + onBackClick: () -> Unit = {}, +) { + TopNavigation( + leftIcon = { + NoRippleIconButton( + onClick = { onBackClick() }, + iconContentDescription = stringResource(R.string.back_sign), + iconPainter = painterResource(id = R.drawable.ic_arrow_left), + ) + }, + title = stringResource(R.string.notice_actionbar_title), + ) +} + +@OptIn(ExperimentalSharedTransitionApi::class) +@Composable +fun Notice( + notice: Notice = Notice( + noticeId = Long.MAX_VALUE, + title = "공지사항", + uploadDate = "0000.00.00" + ), + onNoticeDetailClick: () -> Unit = {}, + sharedElementScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedElementScope) { + Column( + modifier = Modifier + .clickable { onNoticeDetailClick() } + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 20.dp, vertical = 12.dp), + ) { + Text( + text = notice.uploadDate, + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "uploadDate_${notice.noticeId}"), + animatedVisibilityScope = animatedVisibilityScope, + ) + .wrapContentSize(), + style = DayoTheme.typography.caption4, + color = Gray3_9FA5AE, + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = notice.title, + modifier = Modifier + .sharedBounds( + sharedContentState = rememberSharedContentState(key = "title_${notice.noticeId}"), + animatedVisibilityScope = animatedVisibilityScope, + ) + .wrapContentSize(), + style = DayoTheme.typography.b6, + color = Dark, + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt index 204ebd3d..1d770cc4 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsNavigation.kt @@ -26,6 +26,7 @@ fun NavGraphBuilder.settingsNavGraph( onBackClick: () -> Unit, onSettingNotificationClick: () -> Unit, onPasswordChangeClick: () -> Unit, + onNoticesClick: () -> Unit, ) { composable(SettingsRoute.route) { SettingsScreen( @@ -34,6 +35,7 @@ fun NavGraphBuilder.settingsNavGraph( onPasswordChangeClick = onPasswordChangeClick, onBlockUsersClick = onBlockUsersClick, onSettingNotificationClick = onSettingNotificationClick, + onNoticesClick = onNoticesClick, ) } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt index 78fcab48..cc8ad657 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt @@ -62,6 +62,7 @@ fun SettingsScreen( onPasswordChangeClick: () -> Unit, onBlockUsersClick: () -> Unit, onSettingNotificationClick: () -> Unit, + onNoticesClick: () -> Unit, profileViewModel: ProfileViewModel = hiltViewModel() ) { val profileInfo = profileViewModel.profileInfo.observeAsState() @@ -76,6 +77,7 @@ fun SettingsScreen( onBackClick = onBackClick, onSettingNotificationClick = onSettingNotificationClick, onPasswordChangeClick = onPasswordChangeClick, + onNoticesClick = onNoticesClick, onBlockUsersClick = onBlockUsersClick ) } @@ -87,6 +89,7 @@ private fun SettingsScreen( onBackClick: () -> Unit, onSettingNotificationClick: () -> Unit, onPasswordChangeClick: () -> Unit, + onNoticesClick: () -> Unit = {}, onBlockUsersClick: () -> Unit, ) { Scaffold( @@ -126,7 +129,7 @@ private fun SettingsScreen( SettingItem(R.string.setting_menu_block_user, R.drawable.ic_block, onClickMenu = onBlockUsersClick), SettingItem(R.string.setting_menu_notification, R.drawable.ic_notification, onClickMenu = onSettingNotificationClick), null, // Divider - SettingItem(R.string.setting_menu_notice, R.drawable.ic_setting_notice, onClickMenu = {}), + SettingItem(R.string.setting_menu_notice, R.drawable.ic_setting_notice, onClickMenu = onNoticesClick), SettingItem(R.string.setting_menu_information, R.drawable.ic_setting_information, onClickMenu = {}, description = appVersion), SettingItem(R.string.setting_menu_contact, R.drawable.ic_setting_contact, onClickMenu = {}), null // Divider @@ -279,8 +282,9 @@ private fun PreviewSettingsScreen() { onBackClick = {}, onProfileEditClick = {}, onPasswordChangeClick = {}, - onBlockUsersClick = {}, - onSettingNotificationClick = {} + onSettingNotificationClick = {}, + onNoticesClick = {}, + onBlockUsersClick = {} ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/viewmodel/NoticeViewModel.kt b/presentation/src/main/java/daily/dayo/presentation/viewmodel/NoticeViewModel.kt index 3405cc9d..d1c7ee5c 100644 --- a/presentation/src/main/java/daily/dayo/presentation/viewmodel/NoticeViewModel.kt +++ b/presentation/src/main/java/daily/dayo/presentation/viewmodel/NoticeViewModel.kt @@ -1,7 +1,5 @@ package daily.dayo.presentation.viewmodel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData @@ -11,7 +9,9 @@ import daily.dayo.domain.model.Notice import daily.dayo.domain.usecase.notice.RequestAllNoticeListUseCase import daily.dayo.domain.usecase.notice.RequestDetailNoticeUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import daily.dayo.presentation.common.Resource import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -26,8 +26,15 @@ class NoticeViewModel @Inject constructor( private val _noticeList = MutableStateFlow>(PagingData.empty()) val noticeList = _noticeList.asStateFlow() - private val _detailNotice = MutableLiveData() - val detailNotice: LiveData get() = _detailNotice + private val _detailNotice = MutableStateFlow?>(null) + val detailNotice: StateFlow?> get() = _detailNotice + + private val _selectedNotice = MutableStateFlow(null) + val selectedNotice: StateFlow get() = _selectedNotice + + fun selectNotice(notice: Notice) { + _selectedNotice.value = notice + } fun requestAllNoticeList() = viewModelScope.launch { requestAllNoticeListUseCase() @@ -35,11 +42,11 @@ class NoticeViewModel @Inject constructor( .collectLatest { _noticeList.emit(it) } } - fun requestDetailNotice(noticeId: Int) = viewModelScope.launch { + fun requestDetailNotice(noticeId: Long) = viewModelScope.launch { requestDetailNoticeUseCase(noticeId).let { ApiResponse -> when (ApiResponse) { - is NetworkResponse.Success -> _detailNotice.postValue(ApiResponse.body?.contents.toString()) - else -> _detailNotice.postValue("") + is NetworkResponse.Success -> _detailNotice.emit(Resource.success(ApiResponse.body?.contents.toString())) + else -> _detailNotice.emit(Resource.error(ApiResponse.toString(), null)) } } } diff --git a/presentation/src/main/res/layout/fragment_notice_detail.xml b/presentation/src/main/res/layout/fragment_notice_detail.xml deleted file mode 100644 index 5da95fb8..00000000 --- a/presentation/src/main/res/layout/fragment_notice_detail.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_notice_list.xml b/presentation/src/main/res/layout/fragment_notice_list.xml deleted file mode 100644 index bf7bc78d..00000000 --- a/presentation/src/main/res/layout/fragment_notice_list.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/layout/item_notice_post.xml b/presentation/src/main/res/layout/item_notice_post.xml deleted file mode 100644 index b11538ce..00000000 --- a/presentation/src/main/res/layout/item_notice_post.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/navigation/nav_graph.xml b/presentation/src/main/res/navigation/nav_graph.xml index 4cc2b1fe..caa99baa 100644 --- a/presentation/src/main/res/navigation/nav_graph.xml +++ b/presentation/src/main/res/navigation/nav_graph.xml @@ -545,14 +545,6 @@ app:exitAnim="@anim/translate_to_left_out" app:popEnterAnim="@anim/translate_from_left_in" app:popExitAnim="@anim/translate_to_right_out" /> - - - - - - - - - - \ No newline at end of file