From cb46dbd7c6f71bb2c328e585cfa28bb0a3b1d4fc Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Mon, 12 Feb 2024 12:14:26 +0900 Subject: [PATCH 01/24] #546 [layout] Add Emoji View --- presentation/build.gradle | 9 ++- .../home/HomeDayoPickPostListFragment.kt | 73 +++++++++++++++++-- .../daily/dayo/presentation/view/EmojiView.kt | 34 +++++++++ presentation/src/main/res/values/strings.xml | 1 + 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt diff --git a/presentation/build.gradle b/presentation/build.gradle index fc57e508..2520a257 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -27,7 +27,7 @@ android { versionCode 10000 versionName "1.0.0" - buildConfigField ("String", "NATIVE_APP_KEY", properties['NATIVE_APP_KEY_STR']) + buildConfigField("String", "NATIVE_APP_KEY", properties['NATIVE_APP_KEY_STR']) manifestPlaceholders = [NATIVE_APP_KEY: NATIVE_APP_KEY] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -93,6 +93,7 @@ dependencies { def landscapist_glide_version = "2.2.5" def fragment_version = "1.6.1" def activity_version = "1.7.2" + def emoji_version = "1.0.0-alpha03" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.10.1' @@ -111,7 +112,7 @@ dependencies { // Jetpack Compose // Material implementation("androidx.compose.material3:material3") - implementation ("androidx.compose.material:material-icons-extended:1.5.3") + implementation("androidx.compose.material:material-icons-extended:1.5.3") // Android Studio Preview support implementation("androidx.compose.ui:ui-tooling-preview") debugImplementation("androidx.compose.ui:ui-tooling") @@ -221,6 +222,10 @@ dependencies { // optional - Jetpack Compose integration implementation "androidx.paging:paging-compose:3.2.0" + // emoji + implementation "androidx.emoji2:emoji2:$emoji_version" + implementation "androidx.emoji2:emoji2-views:$emoji_version" + testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index 4d47f7ab..c13815c7 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -5,6 +5,23 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -14,20 +31,20 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager +import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.domain.model.Category +import daily.dayo.domain.model.Post import daily.dayo.presentation.R +import daily.dayo.presentation.activity.MainActivity +import daily.dayo.presentation.adapter.HomeDayoPickAdapter import daily.dayo.presentation.common.GlideLoadUtil.loadImageBackground import daily.dayo.presentation.common.Status import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding -import daily.dayo.presentation.adapter.HomeDayoPickAdapter +import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.viewmodel.HomeViewModel -import com.google.android.material.bottomnavigation.BottomNavigationView -import dagger.hilt.android.AndroidEntryPoint -import daily.dayo.domain.model.Category -import daily.dayo.domain.model.Post -import daily.dayo.presentation.activity.MainActivity import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -52,7 +69,14 @@ class HomeDayoPickPostListFragment : Fragment() { ): View? { binding = FragmentHomeDayoPickPostListBinding.inflate(inflater, container, false) glideRequestManager = Glide.with(this) - return binding.root + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme { + HomeDayoPickScreen() + } + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -83,6 +107,31 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } + @Composable + private fun HomeDayoPickScreen() { + Box(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + ) { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + } + } + private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { loadPosts(homeViewModel.currentDayoPickCategory) @@ -129,8 +178,10 @@ class HomeDayoPickPostListFragment : Fragment() { binding.layoutDayopickPostEmpty.isVisible = postList.isEmpty() } } + Status.LOADING -> { } + Status.ERROR -> { } @@ -285,4 +336,12 @@ class HomeDayoPickPostListFragment : Fragment() { rvDayopickPost.visibility = View.VISIBLE } } + + @Composable + @Preview(showBackground = true) + private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeDayoPickScreen() + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt b/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt new file mode 100644 index 00000000..6066dcb3 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt @@ -0,0 +1,34 @@ +package daily.dayo.presentation.view + +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color.Companion.Black +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.viewinterop.AndroidView + +@Composable +fun EmojiView( + emoji: String, + emojiSize: TextUnit, + modifier: Modifier = Modifier +) { + AndroidView( + modifier = modifier, + factory = { context -> + AppCompatTextView(context).apply { + setTextColor(Black.toArgb()) + text = emoji + textSize = emojiSize.value + textAlignment = View.TEXT_ALIGNMENT_CENTER + } + }, + update = { + it.apply { + text = emoji + } + }, + ) +} \ 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 9f8d1b9a..1e8be044 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ DAYO PICK New + 지금 인기있는 게시글이에요! 인기있는 다꾸가 아직 없어요 다꾸들을 구경하고 반응을 남겨보세요 구경하러 가기 From 390fabc619e16bfedf1ded4259c79d18655bf80d Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 00:55:30 +0900 Subject: [PATCH 02/24] #546 [layout] add category menu --- .../home/HomeDayoPickPostListFragment.kt | 94 +++++++++++++++++++ .../src/main/res/drawable/ic_category_all.xml | 33 +++++++ .../main/res/drawable/ic_category_digital.xml | 21 +++++ .../src/main/res/drawable/ic_category_etc.xml | 15 +++ .../res/drawable/ic_category_pocketbook.xml | 12 +++ .../res/drawable/ic_category_scheduler.xml | 32 +++++++ .../res/drawable/ic_category_sixholediary.xml | 36 +++++++ .../res/drawable/ic_category_studyplanner.xml | 15 +++ presentation/src/main/res/values/strings.xml | 3 +- 9 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 presentation/src/main/res/drawable/ic_category_all.xml create mode 100644 presentation/src/main/res/drawable/ic_category_digital.xml create mode 100644 presentation/src/main/res/drawable/ic_category_etc.xml create mode 100644 presentation/src/main/res/drawable/ic_category_pocketbook.xml create mode 100644 presentation/src/main/res/drawable/ic_category_scheduler.xml create mode 100644 presentation/src/main/res/drawable/ic_category_sixholediary.xml create mode 100644 presentation/src/main/res/drawable/ic_category_studyplanner.xml diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index c13815c7..1d9b73d8 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -1,25 +1,42 @@ package daily.dayo.presentation.fragment.home +import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.view.isVisible @@ -43,9 +60,12 @@ import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding +import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -107,8 +127,14 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } + @SuppressLint("CoroutineCreationDuringComposition") + @OptIn(ExperimentalMaterialApi::class) @Composable private fun HomeDayoPickScreen() { + var selectedCategory by rememberSaveable { mutableStateOf(Pair(getString(R.string.all), 0)) } + val coroutineScope = rememberCoroutineScope() + val bottomSheetState = getBottomSheetDialogState() + Box(modifier = Modifier.fillMaxSize()) { Row( modifier = Modifier @@ -128,10 +154,78 @@ class HomeDayoPickPostListFragment : Fragment() { .padding(horizontal = 4.dp) .align(Alignment.CenterVertically) ) + + CategoryMenu(selectedCategory.first, coroutineScope, bottomSheetState) } + + BottomSheetDialog( + sheetState = bottomSheetState, + buttons = listOf( + Pair(stringResource(id = R.string.all)) { + selectedCategory = Pair(getString(R.string.all), 0) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.scheduler)) { + selectedCategory = Pair(getString(R.string.scheduler), 1) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.studyplanner)) { + selectedCategory = Pair(getString(R.string.studyplanner), 2) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.pocketbook)) { + selectedCategory = Pair(getString(R.string.pocketbook), 3) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.sixHoleDiary)) { + selectedCategory = Pair(getString(R.string.sixHoleDiary), 4) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.digital)) { + selectedCategory = Pair(getString(R.string.digital), 5) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.etc)) { + selectedCategory = Pair(getString(R.string.etc), 6) + coroutineScope.launch { bottomSheetState.hide() } + } + ), + title = stringResource(id = R.string.filter), + leftIconButtons = listOf( + ImageVector.vectorResource(R.drawable.ic_category_all), + ImageVector.vectorResource(R.drawable.ic_category_scheduler), + ImageVector.vectorResource(R.drawable.ic_category_studyplanner), + ImageVector.vectorResource(R.drawable.ic_category_pocketbook), + ImageVector.vectorResource(R.drawable.ic_category_sixholediary), + ImageVector.vectorResource(R.drawable.ic_category_digital), + ImageVector.vectorResource(R.drawable.ic_category_etc), + ), + checkedButtonIndex = selectedCategory.second, + closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } + ) } } + @OptIn(ExperimentalMaterialApi::class) + @Composable + private fun CategoryMenu( + selectedCategory: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState + ) { + OutlinedTextField( + value = selectedCategory, + onValueChange = { }, + trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, + readOnly = true, + enabled = false, + modifier = Modifier.clickable( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + indication = null, + interactionSource = remember { MutableInteractionSource() }), + ) + } + private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { loadPosts(homeViewModel.currentDayoPickCategory) diff --git a/presentation/src/main/res/drawable/ic_category_all.xml b/presentation/src/main/res/drawable/ic_category_all.xml new file mode 100644 index 00000000..b5a7c13e --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_all.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_digital.xml b/presentation/src/main/res/drawable/ic_category_digital.xml new file mode 100644 index 00000000..347859d0 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_digital.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_etc.xml b/presentation/src/main/res/drawable/ic_category_etc.xml new file mode 100644 index 00000000..23c6e193 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_etc.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_pocketbook.xml b/presentation/src/main/res/drawable/ic_category_pocketbook.xml new file mode 100644 index 00000000..4c3916f0 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_pocketbook.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_scheduler.xml b/presentation/src/main/res/drawable/ic_category_scheduler.xml new file mode 100644 index 00000000..0f1c1fd6 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_scheduler.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_sixholediary.xml b/presentation/src/main/res/drawable/ic_category_sixholediary.xml new file mode 100644 index 00000000..cf24a826 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_sixholediary.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_studyplanner.xml b/presentation/src/main/res/drawable/ic_category_studyplanner.xml new file mode 100644 index 00000000..7f98c563 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_studyplanner.xml @@ -0,0 +1,15 @@ + + + + + \ 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 1e8be044..03a94f94 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -72,12 +72,13 @@ 공유하러 가기 + 필터 전체 스케줄러 스터디 플래너 포켓북 6공 다이어리 - 굿노트 + 모바일 다이어리 기타 ALL SCHEDULER From 4641a0432f40af5d2a262291510a313f8b7e27cc Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 03:00:16 +0900 Subject: [PATCH 03/24] #546 [layout] Top App Bar in Home Screen --- .../fragment/home/HomeFragment.kt | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt index 5a01c82a..bcd8362b 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt @@ -4,16 +4,42 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R +import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.databinding.FragmentHomeBinding -import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter -import com.google.android.material.tabs.TabLayoutMediator -import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.TextButton +import daily.dayo.presentation.view.TopNavigation const val HOME_DAYOPICK_PAGE_TAB_ID = 0 const val HOME_NEW_PAGE_TAB_ID = 1 @@ -37,7 +63,14 @@ class HomeFragment : Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentHomeBinding.inflate(inflater, container, false) - return binding.root + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme { + HomeScreen() + } + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -50,12 +83,67 @@ class HomeFragment : Fragment() { mediator?.detach() mediator = null pagerAdapter = null - with (binding.pagerHomePost) { + with(binding.pagerHomePost) { unregisterOnPageChangeCallback(pageChangeCallBack) adapter = null } } + @Composable + private fun HomeScreen() { + var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + Scaffold( + topBar = { + TopNavigation( + leftIcon = { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(start = 18.dp) + ) { + TextButton( + onClick = { + homeTabState = HOME_DAYOPICK_PAGE_TAB_ID + }, + text = stringResource(id = R.string.DayoPick), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + + TextButton( + onClick = { + homeTabState = HOME_NEW_PAGE_TAB_ID + }, + text = stringResource(id = R.string.New), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + } + }, + rightIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), + contentDescription = "search", + tint = Gray1_313131, + modifier = Modifier + .padding(end = 12.dp) + .size(24.dp) + ) + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + + } + } + } + private fun setSearchClickListener() { binding.btnPostSearch.setOnDebounceClickListener { findNavController().navigate(R.id.action_homeFragment_to_searchFragment) @@ -92,4 +180,12 @@ class HomeFragment : Fragment() { } mediator?.attach() } + + @Composable + @Preview(showBackground = true) + private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeScreen() + } + } } \ No newline at end of file From d27e249cf4dd97195a3805bc18c61c6f1194b885 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 15:58:22 +0900 Subject: [PATCH 04/24] #546 [layout] Main Bottom Navigation --- .../presentation/activity/MainActivity.kt | 158 +++++++++++++++++- .../presentation/screen/home/HomeScreen.kt | 105 ++++++++++++ presentation/src/main/res/values/strings.xml | 2 +- 3 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt 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 c341d73d..af91c6d1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt +++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt @@ -11,22 +11,60 @@ import android.view.MotionEvent import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.BottomNavigation +import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.view.forEach import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController +import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R import daily.dayo.presentation.databinding.ActivityMainBinding import daily.dayo.presentation.fragment.home.HomeFragmentDirections -import daily.dayo.presentation.viewmodel.SettingNotificationViewModel -import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.presentation.screen.home.HomeScreen +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.AccountViewModel +import daily.dayo.presentation.viewmodel.SettingNotificationViewModel +import kotlinx.coroutines.CoroutineScope @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -36,7 +74,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) + // setContentView(binding.root) setSystemBackClickListener() checkCurrentNotification() initBottomNavigation() @@ -44,6 +82,108 @@ class MainActivity : AppCompatActivity() { disableBottomNaviTooltip() getNotificationData() askNotificationPermission() + setContent { + MainScreen() + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun MainScreen() { + val navController = rememberNavController() + val coroutineScope = rememberCoroutineScope() + val bottomSheetState = getBottomSheetDialogState() + + Scaffold( + bottomBar = { + MainBottomNavigation(navController = navController) + } + ) { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavigationGraph(navController = navController, coroutineScope, bottomSheetState) + } + } + } + + @Composable + fun MainBottomNavigation(navController: NavController) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + val items = listOf( + Screen.Home, + Screen.Feed, + Screen.Write, + Screen.Notification, + Screen.MyPage + ) + + BottomNavigation( + backgroundColor = White_FFFFFF, + contentColor = Gray2_767B83, + modifier = Modifier.height(73.dp) + ) { + items.forEach { screen -> + val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true + BottomNavigationItem( + icon = { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = ImageVector.vectorResource(id = if (selected) screen.selectedIcon else screen.defaultIcon), + contentDescription = stringResource(id = screen.resourceId), + modifier = Modifier + .size(if (screen.route != Screen.Write.route) 24.dp else 36.dp) + ) + + if (screen.route != Screen.Write.route) { + Text(text = stringResource(screen.resourceId), style = MaterialTheme.typography.caption) + } + } + }, + selected = selected, + selectedContentColor = Gray1_313131, + onClick = { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun NavigationGraph( + navController: NavHostController, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState + ) { + NavHost( + navController = navController, + startDestination = Screen.Home.route + ) { + composable(Screen.Home.route) { + HomeScreen(navController, coroutineScope, bottomSheetState) + } + composable(Screen.Feed.route) { + + } + composable(Screen.Write.route) { + + } + composable(Screen.Notification.route) { + + } + composable(Screen.MyPage.route) { + + } + } } private fun setSystemBackClickListener() { @@ -137,6 +277,7 @@ class MainActivity : AppCompatActivity() { ).show() } } + else -> { //All request are permitted // 알림 최초 허용시에 모든 알림 허용처리 @@ -204,12 +345,14 @@ class MainActivity : AppCompatActivity() { isInside = true return true } + MotionEvent.ACTION_MOVE -> { isInside = rect.contains(v!!.left + event.x.toInt(), v.top + event.y.toInt()) binding.bottomNavigationMainBar.clearFocus() return false } + MotionEvent.ACTION_UP -> { binding.bottomNavigationMainBar.menu.findItem(R.id.WriteFragment) .setIcon(R.drawable.ic_write) @@ -223,6 +366,7 @@ class MainActivity : AppCompatActivity() { } return true } + else -> return true } } @@ -249,4 +393,12 @@ class MainActivity : AppCompatActivity() { companion object { val notificationPermission = arrayOf(Manifest.permission.POST_NOTIFICATIONS) } + + sealed class Screen(val route: String, @StringRes val resourceId: Int, @DrawableRes val defaultIcon: Int, @DrawableRes val selectedIcon: Int) { + object Home : Screen("home", R.string.home, R.drawable.ic_home, R.drawable.ic_home_filled) + object Feed : Screen("feed", R.string.feed, R.drawable.ic_feed, R.drawable.ic_feed_filled) + object Write : Screen("write", R.string.write, R.drawable.ic_write, R.drawable.ic_write_filled) + object Notification : Screen("notification", R.string.notification, R.drawable.ic_notification, R.drawable.ic_notification_filled) + object MyPage : Screen("mypage", R.string.my_page, R.drawable.ic_my_page, R.drawable.ic_my_page_filled) + } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt new file mode 100644 index 00000000..8e6edab2 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -0,0 +1,105 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import daily.dayo.presentation.R +import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID +import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.TextButton +import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.view.getBottomSheetDialogState +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeScreen( + navController: NavController, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + Scaffold( + topBar = { + TopNavigation( + leftIcon = { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(start = 18.dp) + ) { + TextButton( + onClick = { + homeTabState = HOME_DAYOPICK_PAGE_TAB_ID + }, + text = stringResource(id = R.string.DayoPick), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + + TextButton( + onClick = { + homeTabState = HOME_NEW_PAGE_TAB_ID + }, + text = stringResource(id = R.string.New), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + } + }, + rightIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), + contentDescription = "search", + tint = Gray1_313131, + modifier = Modifier + .padding(end = 12.dp) + .size(24.dp) + ) + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +@Preview(showBackground = true) +private fun PreviewHomeScreen() { + MaterialTheme { + HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState()) + } +} \ 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 03a94f94..cdc1bad6 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ 피드 글쓰기 알림 - 마이 페이지 + MY 다음 확인 완료 From a6195a515be0fef40c40640f970d459e2718385e Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 18:46:10 +0900 Subject: [PATCH 05/24] #546 [layout] Set bottom sheet dialog to appear above bottom navigation bar --- .../presentation/activity/MainActivity.kt | 31 ++++-- .../screen/home/HomeDayoPickScreen.kt | 94 +++++++++++++++++++ .../presentation/screen/home/HomeScreen.kt | 68 +++++++++++++- 3 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt 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 af91c6d1..0c01ff5d 100644 --- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt +++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt @@ -1,6 +1,7 @@ package daily.dayo.presentation.activity import android.Manifest +import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.graphics.Rect @@ -32,7 +33,10 @@ import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.runtime.Composable 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -87,20 +91,30 @@ class MainActivity : AppCompatActivity() { } } + @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalMaterialApi::class) @Composable fun MainScreen() { val navController = rememberNavController() val coroutineScope = rememberCoroutineScope() val bottomSheetState = getBottomSheetDialogState() + var bottomSheet: (@Composable () -> Unit)? by remember { mutableStateOf(null) } + val bottomSheetContent: (@Composable () -> Unit) -> Unit = { + bottomSheet = it + } Scaffold( - bottomBar = { - MainBottomNavigation(navController = navController) - } - ) { innerPadding -> - Box(Modifier.padding(innerPadding)) { - NavigationGraph(navController = navController, coroutineScope, bottomSheetState) + bottomBar = { bottomSheet?.let { it() } } + ) { + Scaffold( + bottomBar = { + MainBottomNavigation(navController = navController) + + } + ) { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavigationGraph(navController = navController, coroutineScope, bottomSheetState, bottomSheetContent) + } } } } @@ -162,14 +176,15 @@ class MainActivity : AppCompatActivity() { fun NavigationGraph( navController: NavHostController, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + bottomSheetContent: (@Composable () -> Unit) -> Unit ) { NavHost( navController = navController, startDestination = Screen.Home.route ) { composable(Screen.Home.route) { - HomeScreen(navController, coroutineScope, bottomSheetState) + HomeScreen(navController, coroutineScope, bottomSheetState, bottomSheetContent) } composable(Screen.Feed.route) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt new file mode 100644 index 00000000..b13239c3 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -0,0 +1,94 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import daily.dayo.presentation.R +import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.getBottomSheetDialogState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeDayoPickScreen( + selectedCategoryName: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + Box(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + ) { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun CategoryButton( + selectedCategory: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + OutlinedTextField( + value = selectedCategory, + onValueChange = { }, + trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, + readOnly = true, + enabled = false, + modifier = Modifier.clickable( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + indication = null, + interactionSource = remember { MutableInteractionSource() }), + ) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +@Preview(showBackground = true) +private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeDayoPickScreen(CategoryMenu.All.name, rememberCoroutineScope(), getBottomSheetDialogState()) + } +} + diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 8e6edab2..4129d061 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -1,5 +1,6 @@ package daily.dayo.presentation.screen.home +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -13,6 +14,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -30,19 +32,28 @@ import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID import daily.dayo.presentation.theme.Gray1_313131 import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.TextButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.getBottomSheetDialogState import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @Composable fun HomeScreen( navController: NavController, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + bottomSheetContent: (@Composable () -> Unit) -> Unit ) { var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + var selectedCategory by rememberSaveable { mutableStateOf(Pair(CategoryMenu.All.name, 0)) } // name, index + val onCategorySelected: (String, Int) -> Unit = { category, index -> + selectedCategory = Pair(category, index) + coroutineScope.launch { bottomSheetState.hide() } + } + Scaffold( topBar = { TopNavigation( @@ -90,9 +101,47 @@ fun HomeScreen( Column( modifier = Modifier.padding(innerPadding) ) { - + HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState) } } + + bottomSheetContent { + CategoryBottomSheetDialog(onCategorySelected, selectedCategory, coroutineScope, bottomSheetState) + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun CategoryBottomSheetDialog( + onCategorySelected: (String, Int) -> Unit, + selectedCategory: Pair, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + val categoryMenus = listOf( + CategoryMenu.All, + CategoryMenu.Scheduler, + CategoryMenu.StudyPlanner, + CategoryMenu.PocketBook, + CategoryMenu.SixHoleDiary, + CategoryMenu.Digital, + CategoryMenu.ETC + ) + + BottomSheetDialog( + sheetState = bottomSheetState, + buttons = categoryMenus.mapIndexed { index, category -> + Pair(category.name) { + onCategorySelected(category.name, index) + } + }, + title = stringResource(id = R.string.filter), + leftIconButtons = categoryMenus.map { + ImageVector.vectorResource(it.defaultIcon) + }, + checkedButtonIndex = selectedCategory.second, + closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } + ) } @OptIn(ExperimentalMaterialApi::class) @@ -100,6 +149,17 @@ fun HomeScreen( @Preview(showBackground = true) private fun PreviewHomeScreen() { MaterialTheme { - HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState()) + HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState(), {}) } -} \ No newline at end of file +} + + +sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int) { + object All : CategoryMenu("전체", R.drawable.ic_category_all) + object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler) + object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner) + object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook) + object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary) + object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital) + object ETC : CategoryMenu("기타", R.drawable.ic_category_etc) +} From 58170d85aa7c13c6238fdbc3f8932e0fa402857c Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 19:27:42 +0900 Subject: [PATCH 06/24] #546 [layout] dayo pick description placement --- .../screen/home/HomeDayoPickScreen.kt | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index b13239c3..ea6fde03 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -1,23 +1,26 @@ package daily.dayo.presentation.screen.home -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -26,6 +29,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import daily.dayo.presentation.R +import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.Gray6_F0F1F3 +import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.view.getBottomSheetDialogState import kotlinx.coroutines.CoroutineScope @@ -38,26 +45,35 @@ fun HomeDayoPickScreen( coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState ) { - Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 18.dp) + ) { + // description Row( modifier = Modifier .wrapContentHeight() .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } } @@ -70,17 +86,18 @@ private fun CategoryButton( coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState ) { - OutlinedTextField( - value = selectedCategory, - onValueChange = { }, - trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, - readOnly = true, - enabled = false, - modifier = Modifier.clickable( - onClick = { coroutineScope.launch { bottomSheetState.show() } }, - indication = null, - interactionSource = remember { MutableInteractionSource() }), - ) + Button( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(top = 6.dp, bottom = 6.dp, start = 12.dp, end = 4.dp), + colors = androidx.compose.material3.ButtonDefaults.buttonColors(containerColor = White_FFFFFF, contentColor = Gray2_767B83), + modifier = Modifier + .border(1.dp, Gray6_F0F1F3, shape = RoundedCornerShape(8.dp)) + ) { + Text(text = selectedCategory, style = MaterialTheme.typography.caption3) + Spacer(modifier = Modifier.width(8.dp)) + Icon(Icons.Filled.ArrowDropDown, "category menu") + } } @OptIn(ExperimentalMaterialApi::class) From efa15b85d075a50b7c4bbbb67c9f863ae72eccc7 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Thu, 22 Feb 2024 00:36:15 +0900 Subject: [PATCH 07/24] #546 [layout] Show Dayo Pick Post List --- presentation/build.gradle | 6 +- .../screen/home/HomeDayoPickScreen.kt | 102 +++++++++++++----- .../dayo/presentation/view/HomePostView.kt | 34 ++++++ 3 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt diff --git a/presentation/build.gradle b/presentation/build.gradle index 2520a257..0fa5be7e 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -140,7 +140,8 @@ dependencies { // Testing Navigation androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" // Jetpack Compose Integration - implementation("androidx.navigation:navigation-compose:$nav_version") + implementation "androidx.navigation:navigation-compose:$nav_version" + implementation "androidx.hilt:hilt-navigation-compose:1.1.0" // ViewPager2 implementation "androidx.viewpager2:viewpager2:1.0.0" @@ -199,6 +200,9 @@ dependencies { // Glide-For-Compose implementation "com.github.skydoves:landscapist-glide:$landscapist_glide_version" + // Coil + implementation "io.coil-kt:coil-compose:2.5.0" + // Preference implementation 'androidx.preference:preference-ktx:1.1.1' diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index ea6fde03..d241e365 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -2,15 +2,19 @@ package daily.dayo.presentation.screen.home import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState @@ -21,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -28,13 +33,18 @@ import androidx.compose.ui.graphics.Color 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 daily.dayo.domain.model.Category import daily.dayo.presentation.R +import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 import daily.dayo.presentation.theme.Gray6_F0F1F3 import daily.dayo.presentation.theme.White_FFFFFF import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.view.getBottomSheetDialogState +import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -43,42 +53,81 @@ import kotlinx.coroutines.launch fun HomeDayoPickScreen( selectedCategoryName: String, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + homeViewModel: HomeViewModel = hiltViewModel() ) { - Column( + val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() + loadPosts(homeViewModel, Category.ALL) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), modifier = Modifier .fillMaxSize() - .padding(horizontal = 18.dp) ) { // description - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + + item(span = { GridItemSpan(2) }) { + Spacer(modifier = Modifier.size(12.dp)) + } + + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + dayoPickPostList.value?.data?.let { + items(it) { post -> + HomePostView(post) + } + } + } + + Status.LOADING -> { + } + + Status.ERROR -> { + + } + + else -> {} } } } +private fun loadPosts(homeViewModel: HomeViewModel, selectCategory: Category) { + with(homeViewModel) { + currentDayoPickCategory = selectCategory + requestDayoPickPostList() + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable private fun CategoryButton( @@ -109,3 +158,4 @@ private fun PreviewHomeDayoPickScreen() { } } + diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt new file mode 100644 index 00000000..52a495c1 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -0,0 +1,34 @@ +package daily.dayo.presentation.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import daily.dayo.domain.model.Post +import daily.dayo.presentation.BuildConfig + +@Composable +fun HomePostView(post: Post, modifier: Modifier = Modifier) { + Column( + modifier = modifier.padding(bottom = 10.dp) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") + .build(), + contentDescription = "dayo pick image", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(size = 8.dp)) + ) + } +} \ No newline at end of file From 2c888a70c9af8208e5f89c1aba45f63a4cc2278e Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 28 Feb 2024 18:20:09 +0900 Subject: [PATCH 08/24] #546 [layout] Implementing a HomePostView --- .../screen/home/HomeDayoPickScreen.kt | 14 +- .../dayo/presentation/view/HomePostView.kt | 152 ++++++++++++++++-- .../src/main/res/drawable/ic_dayo_pick.xml | 21 +++ 3 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 presentation/src/main/res/drawable/ic_dayo_pick.xml diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index d241e365..68695746 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState @@ -72,7 +70,7 @@ fun HomeDayoPickScreen( modifier = Modifier .wrapContentHeight() .fillMaxWidth() - .padding(top = 8.dp), + .padding(top = 8.dp, bottom = 12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -95,16 +93,12 @@ fun HomeDayoPickScreen( } } - item(span = { GridItemSpan(2) }) { - Spacer(modifier = Modifier.size(12.dp)) - } - // dayo pick list when (dayoPickPostList.value?.status) { Status.SUCCESS -> { - dayoPickPostList.value?.data?.let { - items(it) { post -> - HomePostView(post) + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView(post = post, isDayoPick = index in 0..4, modifier = Modifier.padding(bottom = 20.dp)) } } } diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt index 52a495c1..3f0524be 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -1,34 +1,158 @@ package daily.dayo.presentation.view +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale 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 import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest import daily.dayo.domain.model.Post import daily.dayo.presentation.BuildConfig +import daily.dayo.presentation.R +import daily.dayo.presentation.common.extension.clickableSingle +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.b5 +import daily.dayo.presentation.theme.caption3 +import java.text.DecimalFormat @Composable -fun HomePostView(post: Post, modifier: Modifier = Modifier) { - Column( - modifier = modifier.padding(bottom = 10.dp) - ) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") - .build(), - contentDescription = "dayo pick image", - contentScale = ContentScale.Crop, +fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean = false) { + val imageInteractionSource = remember { MutableInteractionSource() } + Column(modifier = modifier) { + Box( modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(size = 8.dp)) + .fillMaxWidth() + .aspectRatio(1f) + ) { + // Thumbnail Image + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") + .build(), + contentDescription = "dayo pick image", + contentScale = ContentScale.Crop, + modifier = Modifier + .matchParentSize() + .clip(RoundedCornerShape(size = 8.dp)) + .clickableSingle( + interactionSource = imageInteractionSource, + indication = null, + onClick = { } + ) + ) + + // Dayo Pick Icon + if (isDayoPick) { + Image( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_dayo_pick), + modifier = Modifier + .align(Alignment.TopEnd) + .padding(end = 12.dp), + contentDescription = null + ) + } + + // Like Button + Image( + imageVector = ImageVector.vectorResource(id = if (post.heart) R.drawable.ic_heart_filled else R.drawable.ic_heart), + modifier = Modifier + .padding(bottom = 12.dp, end = 11.dp) + .align(Alignment.BottomEnd) + .clickable { }, + contentDescription = "like Button", + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Publisher Info + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.wrapContentHeight() + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.userProfileImage}") + .build(), + contentDescription = "${post.nickname} + profile", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(16.dp) + .clip(shape = CircleShape) + .align(Alignment.CenterVertically) + .clickableSingle( + interactionSource = imageInteractionSource, + indication = null, + onClick = { } + ) + ) + Text(text = post.nickname, style = MaterialTheme.typography.b5.copy(Gray1_313131)) + } + + Spacer(modifier = Modifier.height(2.dp)) + + // Post Info + val dec = DecimalFormat("#,###") + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Text(text = stringResource(id = R.string.like) + " ${dec.format(post.heartCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) + Text(text = stringResource(id = R.string.comment) + " ${dec.format(post.commentCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) + } + } +} + +@Composable +@Preview(showBackground = true) +private fun PreviewHomePostView() { + MaterialTheme { + HomePostView( + modifier = Modifier.size(156.dp, 205.dp), + post = Post( + postId = 0, + thumbnailImage = "", + memberId = "", + nickname = "nickname", + userProfileImage = "", + heartCount = 123456, + commentCount = 8100, + heart = true, + category = null, + postImages = null, + contents = null, + createDateTime = null, + folderId = null, + folderName = null, + comments = null, + hashtags = null, + bookmark = null + ), + isDayoPick = true ) } -} \ No newline at end of file +} diff --git a/presentation/src/main/res/drawable/ic_dayo_pick.xml b/presentation/src/main/res/drawable/ic_dayo_pick.xml new file mode 100644 index 00000000..75510533 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_dayo_pick.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file From af325f7726f1a3f294da530be32a05fbd1147350 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Thu, 29 Feb 2024 01:27:17 +0900 Subject: [PATCH 09/24] #546 [feature] Add HomeNewScreen --- .../home/HomeDayoPickPostListFragment.kt | 166 +----------------- .../fragment/home/HomeFragment.kt | 106 +---------- .../fragment/home/HomeNewPostListFragment.kt | 12 +- .../screen/home/HomeDayoPickScreen.kt | 141 ++++++++------- .../presentation/screen/home/HomeNewScreen.kt | 126 +++++++++++++ .../presentation/screen/home/HomeScreen.kt | 50 ++++-- .../dayo/presentation/view/HomePostView.kt | 47 +++-- .../presentation/viewmodel/HomeViewModel.kt | 66 +++++-- presentation/src/main/res/values/strings.xml | 1 + 9 files changed, 341 insertions(+), 374 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index 1d9b73d8..589c312c 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -1,44 +1,10 @@ package daily.dayo.presentation.fragment.home -import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -60,12 +26,8 @@ import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding -import daily.dayo.presentation.view.BottomSheetDialog -import daily.dayo.presentation.view.EmojiView -import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -79,8 +41,10 @@ class HomeDayoPickPostListFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + /* if (savedInstanceState == null) loadPosts(homeViewModel.currentDayoPickCategory) + */ } override fun onCreateView( @@ -89,14 +53,7 @@ class HomeDayoPickPostListFragment : Fragment() { ): View? { binding = FragmentHomeDayoPickPostListBinding.inflate(inflater, container, false) glideRequestManager = Glide.with(this) - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme { - HomeDayoPickScreen() - } - } - } + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -127,112 +84,14 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } - @SuppressLint("CoroutineCreationDuringComposition") - @OptIn(ExperimentalMaterialApi::class) - @Composable - private fun HomeDayoPickScreen() { - var selectedCategory by rememberSaveable { mutableStateOf(Pair(getString(R.string.all), 0)) } - val coroutineScope = rememberCoroutineScope() - val bottomSheetState = getBottomSheetDialogState() - - Box(modifier = Modifier.fillMaxSize()) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - ) { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) - - CategoryMenu(selectedCategory.first, coroutineScope, bottomSheetState) - } - - BottomSheetDialog( - sheetState = bottomSheetState, - buttons = listOf( - Pair(stringResource(id = R.string.all)) { - selectedCategory = Pair(getString(R.string.all), 0) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.scheduler)) { - selectedCategory = Pair(getString(R.string.scheduler), 1) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.studyplanner)) { - selectedCategory = Pair(getString(R.string.studyplanner), 2) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.pocketbook)) { - selectedCategory = Pair(getString(R.string.pocketbook), 3) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.sixHoleDiary)) { - selectedCategory = Pair(getString(R.string.sixHoleDiary), 4) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.digital)) { - selectedCategory = Pair(getString(R.string.digital), 5) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.etc)) { - selectedCategory = Pair(getString(R.string.etc), 6) - coroutineScope.launch { bottomSheetState.hide() } - } - ), - title = stringResource(id = R.string.filter), - leftIconButtons = listOf( - ImageVector.vectorResource(R.drawable.ic_category_all), - ImageVector.vectorResource(R.drawable.ic_category_scheduler), - ImageVector.vectorResource(R.drawable.ic_category_studyplanner), - ImageVector.vectorResource(R.drawable.ic_category_pocketbook), - ImageVector.vectorResource(R.drawable.ic_category_sixholediary), - ImageVector.vectorResource(R.drawable.ic_category_digital), - ImageVector.vectorResource(R.drawable.ic_category_etc), - ), - checkedButtonIndex = selectedCategory.second, - closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } - ) - } - } - - @OptIn(ExperimentalMaterialApi::class) - @Composable - private fun CategoryMenu( - selectedCategory: String, - coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState - ) { - OutlinedTextField( - value = selectedCategory, - onValueChange = { }, - trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, - readOnly = true, - enabled = false, - modifier = Modifier.clickable( - onClick = { coroutineScope.launch { bottomSheetState.show() } }, - indication = null, - interactionSource = remember { MutableInteractionSource() }), - ) - } - private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { - loadPosts(homeViewModel.currentDayoPickCategory) + // loadPosts(homeViewModel.currentDayoPickCategory) } } private fun setInitialCategory() { + /* with(binding) { radiogroupDayopickPostCategory.check( when (homeViewModel.currentDayoPickCategory) { @@ -246,6 +105,7 @@ class HomeDayoPickPostListFragment : Fragment() { } ) } + */ } private fun setRvDayoPickPostAdapter() { @@ -309,7 +169,7 @@ class HomeDayoPickPostListFragment : Fragment() { private fun loadPosts(selectCategory: Category, isSmoothScroll: Boolean = false) { with(homeViewModel) { - currentDayoPickCategory = selectCategory + // currentDayoPickCategory = selectCategory requestDayoPickPostList() } @@ -335,7 +195,7 @@ class HomeDayoPickPostListFragment : Fragment() { this@HomeDayoPickPostListFragment.tag, "PostId Null Exception Occurred" ) - loadPosts(homeViewModel.currentDayoPickCategory) + // loadPosts(homeViewModel.currentDayoPickCategory) } } } @@ -406,7 +266,7 @@ class HomeDayoPickPostListFragment : Fragment() { (requireActivity() as MainActivity).setBottomNavigationIconClickListener(reselectedIconId = R.id.HomeFragment) { if (currentViewPagerPosition == HOME_DAYOPICK_PAGE_TAB_ID) { binding.swipeRefreshLayoutDayoPickPost.isRefreshing = true - loadPosts(homeViewModel.currentDayoPickCategory, isSmoothScroll = true) + // loadPosts(homeViewModel.currentDayoPickCategory, isSmoothScroll = true) } } } @@ -430,12 +290,4 @@ class HomeDayoPickPostListFragment : Fragment() { rvDayopickPost.visibility = View.VISIBLE } } - - @Composable - @Preview(showBackground = true) - private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeDayoPickScreen() - } - } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt index bcd8362b..5a01c82a 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt @@ -4,42 +4,16 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.tabs.TabLayoutMediator -import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R -import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.databinding.FragmentHomeBinding -import daily.dayo.presentation.theme.Gray1_313131 -import daily.dayo.presentation.theme.Gray5_E8EAEE -import daily.dayo.presentation.view.TextButton -import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint const val HOME_DAYOPICK_PAGE_TAB_ID = 0 const val HOME_NEW_PAGE_TAB_ID = 1 @@ -63,14 +37,7 @@ class HomeFragment : Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentHomeBinding.inflate(inflater, container, false) - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme { - HomeScreen() - } - } - } + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -83,67 +50,12 @@ class HomeFragment : Fragment() { mediator?.detach() mediator = null pagerAdapter = null - with(binding.pagerHomePost) { + with (binding.pagerHomePost) { unregisterOnPageChangeCallback(pageChangeCallBack) adapter = null } } - @Composable - private fun HomeScreen() { - var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } - Scaffold( - topBar = { - TopNavigation( - leftIcon = { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier.padding(start = 18.dp) - ) { - TextButton( - onClick = { - homeTabState = HOME_DAYOPICK_PAGE_TAB_ID - }, - text = stringResource(id = R.string.DayoPick), - textStyle = MaterialTheme.typography.titleLarge.copy( - color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, - fontWeight = FontWeight.ExtraBold - ) - ) - - TextButton( - onClick = { - homeTabState = HOME_NEW_PAGE_TAB_ID - }, - text = stringResource(id = R.string.New), - textStyle = MaterialTheme.typography.titleLarge.copy( - color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, - fontWeight = FontWeight.ExtraBold - ) - ) - } - }, - rightIcon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), - contentDescription = "search", - tint = Gray1_313131, - modifier = Modifier - .padding(end = 12.dp) - .size(24.dp) - ) - } - ) - } - ) { innerPadding -> - Column( - modifier = Modifier.padding(innerPadding) - ) { - - } - } - } - private fun setSearchClickListener() { binding.btnPostSearch.setOnDebounceClickListener { findNavController().navigate(R.id.action_homeFragment_to_searchFragment) @@ -180,12 +92,4 @@ class HomeFragment : Fragment() { } mediator?.attach() } - - @Composable - @Preview(showBackground = true) - private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeScreen() - } - } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt index ceefafd8..0fab6996 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt @@ -44,8 +44,10 @@ class HomeNewPostListFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + /* if (savedInstanceState == null) loadPosts(homeViewModel.currentNewCategory) + */ } override fun onCreateView( @@ -87,11 +89,12 @@ class HomeNewPostListFragment : Fragment() { private fun setNewPostListRefreshListener() { binding.swipeRefreshLayoutNewPost.setOnRefreshListener { - loadPosts(homeViewModel.currentNewCategory) + // loadPosts(homeViewModel.currentNewCategory) } } private fun setInitialCategory() { + /* with(binding) { radiogroupNewPostCategory.check( when (homeViewModel.currentNewCategory) { @@ -105,6 +108,7 @@ class HomeNewPostListFragment : Fragment() { } ) } + */ } private fun setRvNewPostAdapter() { @@ -164,7 +168,7 @@ class HomeNewPostListFragment : Fragment() { private fun loadPosts(selectCategory: Category, isSmoothScroll: Boolean = false) { with(homeViewModel) { - currentNewCategory = selectCategory + // currentNewCategory = selectCategory requestNewPostList() } @@ -187,7 +191,7 @@ class HomeNewPostListFragment : Fragment() { } } catch (postIdNullException: NullPointerException) { Log.e(this@HomeNewPostListFragment.tag, "PostId Null Exception Occurred") - loadPosts(homeViewModel.currentNewCategory) + // loadPosts(homeViewModel.currentNewCategory) } } } @@ -253,7 +257,7 @@ class HomeNewPostListFragment : Fragment() { (requireActivity() as MainActivity).setBottomNavigationIconClickListener(reselectedIconId = R.id.HomeFragment) { if (currentViewPagerPosition == HOME_NEW_PAGE_TAB_ID) { binding.swipeRefreshLayoutNewPost.isRefreshing = true - loadPosts(homeViewModel.currentNewCategory, isSmoothScroll = true) + // loadPosts(homeViewModel.currentNewCategory, isSmoothScroll = true) } } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index 68695746..0e601543 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -2,6 +2,7 @@ package daily.dayo.presentation.screen.home import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,21 +19,23 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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 daily.dayo.domain.model.Category +import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 @@ -41,7 +44,6 @@ import daily.dayo.presentation.theme.White_FFFFFF import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.view.HomePostView -import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -55,76 +57,91 @@ fun HomeDayoPickScreen( homeViewModel: HomeViewModel = hiltViewModel() ) { val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() - loadPosts(homeViewModel, Category.ALL) + val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() + val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadDayoPickPosts() }) + Box(Modifier.pullRefresh(pullRefreshState)) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // dayo pick list - when (dayoPickPostList.value?.status) { - Status.SUCCESS -> { - dayoPickPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView(post = post, isDayoPick = index in 0..4, modifier = Modifier.padding(bottom = 20.dp)) + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + isDayoPick = index in 0..4, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + } + }, + onClickNickname = { + // todo move to profile + } + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } - } -} -private fun loadPosts(homeViewModel: HomeViewModel, selectCategory: Category) { - with(homeViewModel) { - currentDayoPickCategory = selectCategory - requestDayoPickPostList() + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } } @OptIn(ExperimentalMaterialApi::class) @Composable -private fun CategoryButton( +fun CategoryButton( selectedCategory: String, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState @@ -143,13 +160,3 @@ private fun CategoryButton( } } -@OptIn(ExperimentalMaterialApi::class) -@Composable -@Preview(showBackground = true) -private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeDayoPickScreen(CategoryMenu.All.name, rememberCoroutineScope(), getBottomSheetDialogState()) - } -} - - diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt new file mode 100644 index 00000000..f6d9219f --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt @@ -0,0 +1,126 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import daily.dayo.presentation.R +import daily.dayo.presentation.common.Status +import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.HomePostView +import daily.dayo.presentation.viewmodel.HomeViewModel +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeNewScreen( + selectedCategoryName: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState, + homeViewModel: HomeViewModel = hiltViewModel() +) { + val newPostList = homeViewModel.newPostList.observeAsState() + val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() + val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadNewPosts() }) + Box(Modifier.pullRefresh(pullRefreshState)) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDC40", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_new_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + } + + // new post list + when (newPostList.value?.status) { + Status.SUCCESS -> { + newPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + } + }, + onClickNickname = { + // todo move to profile + } + ) + } + } + } + + Status.LOADING -> { + } + + Status.ERROR -> { + + } + + else -> {} + } + } + + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 4129d061..055e19fb 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -25,32 +25,38 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import daily.dayo.domain.model.Category import daily.dayo.presentation.R -import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID -import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID import daily.dayo.presentation.theme.Gray1_313131 import daily.dayo.presentation.theme.Gray5_E8EAEE import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.TextButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.getBottomSheetDialogState +import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +const val HOME_DAYOPICK_PAGE_TAB_ID = 0 +const val HOME_NEW_PAGE_TAB_ID = 1 + @OptIn(ExperimentalMaterialApi::class) @Composable fun HomeScreen( navController: NavController, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState, - bottomSheetContent: (@Composable () -> Unit) -> Unit + bottomSheetContent: (@Composable () -> Unit) -> Unit, + homeViewModel: HomeViewModel = hiltViewModel() ) { var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } var selectedCategory by rememberSaveable { mutableStateOf(Pair(CategoryMenu.All.name, 0)) } // name, index - val onCategorySelected: (String, Int) -> Unit = { category, index -> - selectedCategory = Pair(category, index) + val onClickCategory: (CategoryMenu, Int) -> Unit = { categoryMenu, index -> + selectedCategory = Pair(categoryMenu.name, index) + homeViewModel.setCategory(categoryMenu.category) coroutineScope.launch { bottomSheetState.hide() } } @@ -101,19 +107,29 @@ fun HomeScreen( Column( modifier = Modifier.padding(innerPadding) ) { - HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState) + when (homeTabState) { + HOME_DAYOPICK_PAGE_TAB_ID -> { + HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState, homeViewModel) + homeViewModel.loadDayoPickPosts() + } + + HOME_NEW_PAGE_TAB_ID -> { + HomeNewScreen(selectedCategory.first, coroutineScope, bottomSheetState, homeViewModel) + homeViewModel.loadNewPosts() + } + } } } bottomSheetContent { - CategoryBottomSheetDialog(onCategorySelected, selectedCategory, coroutineScope, bottomSheetState) + CategoryBottomSheetDialog(onClickCategory, selectedCategory, coroutineScope, bottomSheetState) } } @OptIn(ExperimentalMaterialApi::class) @Composable private fun CategoryBottomSheetDialog( - onCategorySelected: (String, Int) -> Unit, + onCategorySelected: (CategoryMenu, Int) -> Unit, selectedCategory: Pair, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState @@ -132,7 +148,7 @@ private fun CategoryBottomSheetDialog( sheetState = bottomSheetState, buttons = categoryMenus.mapIndexed { index, category -> Pair(category.name) { - onCategorySelected(category.name, index) + onCategorySelected(category, index) } }, title = stringResource(id = R.string.filter), @@ -154,12 +170,12 @@ private fun PreviewHomeScreen() { } -sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int) { - object All : CategoryMenu("전체", R.drawable.ic_category_all) - object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler) - object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner) - object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook) - object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary) - object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital) - object ETC : CategoryMenu("기타", R.drawable.ic_category_etc) +sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int, val category: Category) { + object All : CategoryMenu("전체", R.drawable.ic_category_all, Category.ALL) + object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler, Category.SCHEDULER) + object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner, Category.STUDY_PLANNER) + object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook, Category.POCKET_BOOK) + object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary, Category.SIX_DIARY) + object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital, Category.GOOD_NOTE) + object ETC : CategoryMenu("기타", R.drawable.ic_category_etc, Category.STUDY_PLANNER) } diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt index 3f0524be..34a7aaae 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -1,7 +1,6 @@ package daily.dayo.presentation.view import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -16,6 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -43,7 +43,14 @@ import daily.dayo.presentation.theme.caption3 import java.text.DecimalFormat @Composable -fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean = false) { +fun HomePostView( + post: Post, + modifier: Modifier = Modifier, + isDayoPick: Boolean = false, + onClickPost: () -> Unit, + onClickLikePost: () -> Unit, + onClickNickname: () -> Unit +) { val imageInteractionSource = remember { MutableInteractionSource() } Column(modifier = modifier) { Box( @@ -51,7 +58,7 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .fillMaxWidth() .aspectRatio(1f) ) { - // Thumbnail Image + // thumbnail image AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") @@ -64,11 +71,11 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .clickableSingle( interactionSource = imageInteractionSource, indication = null, - onClick = { } + onClick = { onClickPost() } ) ) - // Dayo Pick Icon + // dayo pick icon if (isDayoPick) { Image( imageVector = ImageVector.vectorResource(id = R.drawable.ic_dayo_pick), @@ -79,23 +86,33 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean ) } - // Like Button + // like button Image( imageVector = ImageVector.vectorResource(id = if (post.heart) R.drawable.ic_heart_filled else R.drawable.ic_heart), modifier = Modifier .padding(bottom = 12.dp, end = 11.dp) .align(Alignment.BottomEnd) - .clickable { }, + .clickableSingle( + indication = rememberRipple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + onClick = { onClickLikePost() } + ), contentDescription = "like Button", ) } Spacer(modifier = Modifier.height(8.dp)) - // Publisher Info + // publisher info Row( horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.wrapContentHeight() + modifier = Modifier + .wrapContentHeight() + .clickableSingle( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { onClickNickname() } + ) ) { AsyncImage( model = ImageRequest.Builder(LocalContext.current) @@ -107,18 +124,13 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .size(16.dp) .clip(shape = CircleShape) .align(Alignment.CenterVertically) - .clickableSingle( - interactionSource = imageInteractionSource, - indication = null, - onClick = { } - ) ) Text(text = post.nickname, style = MaterialTheme.typography.b5.copy(Gray1_313131)) } Spacer(modifier = Modifier.height(2.dp)) - // Post Info + // post info val dec = DecimalFormat("#,###") Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Text(text = stringResource(id = R.string.like) + " ${dec.format(post.heartCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) @@ -152,7 +164,10 @@ private fun PreviewHomePostView() { hashtags = null, bookmark = null ), - isDayoPick = true + isDayoPick = true, + onClickPost = {}, + onClickLikePost = {}, + onClickNickname = {} ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt b/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt index 1b253ea8..69dc2378 100644 --- a/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt +++ b/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import daily.dayo.presentation.common.Resource +import dagger.hilt.android.lifecycle.HiltViewModel import daily.dayo.domain.model.Category import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Post @@ -14,8 +14,11 @@ import daily.dayo.domain.usecase.post.RequestDayoPickPostListCategoryUseCase import daily.dayo.domain.usecase.post.RequestDayoPickPostListUseCase import daily.dayo.domain.usecase.post.RequestNewPostListCategoryUseCase import daily.dayo.domain.usecase.post.RequestNewPostListUseCase -import dagger.hilt.android.lifecycle.HiltViewModel +import daily.dayo.presentation.common.Resource import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -28,8 +31,13 @@ class HomeViewModel @Inject constructor( private val requestLikePostUseCase: RequestLikePostUseCase, private val requestUnlikePostUseCase: RequestUnlikePostUseCase ) : ViewModel() { - var currentDayoPickCategory = Category.ALL - var currentNewCategory = Category.ALL + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing: StateFlow + get() = _isRefreshing.asStateFlow() + + private val _currentCategory = MutableStateFlow(Category.ALL) + val currentCategory: StateFlow + get() = _currentCategory.asStateFlow() private val _dayoPickPostList = MutableLiveData>>() val dayoPickPostList: LiveData>> get() = _dayoPickPostList @@ -37,21 +45,41 @@ class HomeViewModel @Inject constructor( private val _newPostList = MutableLiveData>>() val newPostList: LiveData>> get() = _newPostList + fun setCategory(category: Category) { + viewModelScope.launch { + _currentCategory.emit(category) + } + } + + fun loadDayoPickPosts() { + viewModelScope.launch { + _isRefreshing.emit(true) + requestDayoPickPostList() + _isRefreshing.emit(false) + } + } + + fun loadNewPosts() { + viewModelScope.launch { + _isRefreshing.emit(true) + requestNewPostList() + _isRefreshing.emit(false) + } + } + fun requestDayoPickPostList() = viewModelScope.launch { _dayoPickPostList.postValue(Resource.loading(null)) - if (currentDayoPickCategory == Category.ALL) { - requestHomeDayoPickPostList() - } else { - requestHomeDayoPickPostListCategory(currentDayoPickCategory) + when (currentCategory.value) { + Category.ALL -> requestHomeDayoPickPostList() + else -> requestHomeDayoPickPostListCategory(currentCategory.value) } } fun requestNewPostList() = viewModelScope.launch { _newPostList.postValue(Resource.loading(null)) - if (currentNewCategory == Category.ALL) { - requestHomeNewPostList() - } else { - requestHomeNewPostListCategory(currentNewCategory) + when (currentCategory.value) { + Category.ALL -> requestHomeNewPostList() + else -> requestHomeNewPostListCategory(currentCategory.value) } } @@ -61,12 +89,15 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _newPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _newPostList.postValue(Resource.error(ApiResponse.exception.toString(), null)) } + is NetworkResponse.ApiError -> { _newPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _newPostList.postValue(Resource.error(ApiResponse.throwable.toString(), null)) } @@ -80,12 +111,15 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _newPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _newPostList.postValue(Resource.error(ApiResponse.exception.toString(), null)) } + is NetworkResponse.ApiError -> { _newPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _newPostList.postValue(Resource.error(ApiResponse.throwable.toString(), null)) } @@ -99,6 +133,7 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _dayoPickPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _dayoPickPostList.postValue( Resource.error( @@ -107,9 +142,11 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.ApiError -> { _dayoPickPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _dayoPickPostList.postValue( Resource.error( @@ -128,6 +165,7 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _dayoPickPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _dayoPickPostList.postValue( Resource.error( @@ -136,9 +174,11 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.ApiError -> { _dayoPickPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _dayoPickPostList.postValue( Resource.error( @@ -172,6 +212,7 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.NetworkError -> {} is NetworkResponse.ApiError -> {} is NetworkResponse.UnknownError -> {} @@ -200,6 +241,7 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.NetworkError -> {} is NetworkResponse.ApiError -> {} is NetworkResponse.UnknownError -> {} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index cdc1bad6..486e7da5 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -67,6 +67,7 @@ 인기있는 다꾸가 아직 없어요 다꾸들을 구경하고 반응을 남겨보세요 구경하러 가기 + 실시간으로 올라온 게시글이에요! 새로운 다꾸가 아직 없어요 여러분의 다꾸를 공유해보세요 공유하러 가기 From 7d69f908a78663e09508b3da8857c1e0dffc0cd7 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Fri, 1 Mar 2024 01:51:51 +0900 Subject: [PATCH 10/24] #546 [layout] Home Empty View --- .../screen/home/HomeDayoPickScreen.kt | 170 +++++++++++------- .../presentation/screen/home/HomeNewScreen.kt | 167 ++++++++++------- 2 files changed, 213 insertions(+), 124 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index 0e601543..97642c4f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -1,13 +1,16 @@ package daily.dayo.presentation.screen.home +import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row 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.width import androidx.compose.foundation.layout.wrapContentHeight @@ -29,20 +32,30 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.Gray4_C5CAD2 import daily.dayo.presentation.theme.Gray6_F0F1F3 import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.theme.b3 +import daily.dayo.presentation.theme.caption1 import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.FilledButton import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope @@ -59,86 +72,117 @@ fun HomeDayoPickScreen( val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadDayoPickPosts() }) - Box(Modifier.pullRefresh(pullRefreshState)) { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + var isEmpty by rememberSaveable { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier.wrapContentHeight() + ) { + // description + item(span = { GridItemSpan(maxLineSpan) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // dayo pick list - when (dayoPickPostList.value?.status) { - Status.SUCCESS -> { - dayoPickPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView( - post = post, - isDayoPick = index in 0..4, - modifier = Modifier.padding(bottom = 20.dp), - onClickPost = { - // todo move to post - }, - onClickLikePost = { - if (!post.heart) { - homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) - } else { - homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + isEmpty = dayoPickPostList.value?.data?.isEmpty() == true + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + isDayoPick = index in 0..4, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + } + }, + onClickNickname = { + // todo move to profile } - }, - onClickNickname = { - // todo move to profile - } - ) + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } + + // empty view + if (isEmpty) HomeDayoPickEmptyView() } + // refresh indicator PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } } +@Composable +private fun HomeDayoPickEmptyView() { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image(imageVector = ImageVector.vectorResource(id = R.drawable.ic_home_empty), contentDescription = null) + Spacer(modifier = Modifier.height(20.dp)) + + Text(text = stringResource(id = R.string.home_dayopick_empty_title), style = MaterialTheme.typography.b3.copy(Gray3_9FA5AE)) + Spacer(modifier = Modifier.height(2.dp)) + Text(text = stringResource(id = R.string.home_dayopick_empty_detail), style = MaterialTheme.typography.caption1.copy(Gray4_C5CAD2)) + + Spacer(modifier = Modifier.height(28.dp)) + FilledButton(onClick = { /*TODO*/ }, label = stringResource(id = R.string.home_dayopick_empty_action)) + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable fun CategoryButton( diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt index f6d9219f..af12fd4f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt @@ -1,11 +1,15 @@ package daily.dayo.presentation.screen.home +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +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.lazy.grid.GridCells @@ -21,16 +25,26 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.Gray4_C5CAD2 +import daily.dayo.presentation.theme.b3 +import daily.dayo.presentation.theme.caption1 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.FilledButton import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope @@ -46,81 +60,112 @@ fun HomeNewScreen( val newPostList = homeViewModel.newPostList.observeAsState() val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadNewPosts() }) - Box(Modifier.pullRefresh(pullRefreshState)) { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDC40", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) + var isEmpty by rememberSaveable { mutableStateOf(false) } - Text( - text = stringResource(id = R.string.home_new_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier.wrapContentHeight() + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDC40", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_new_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // new post list - when (newPostList.value?.status) { - Status.SUCCESS -> { - newPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView( - post = post, - modifier = Modifier.padding(bottom = 20.dp), - onClickPost = { - // todo move to post - }, - onClickLikePost = { - if (!post.heart) { - homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) - } else { - homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + // new post list + when (newPostList.value?.status) { + Status.SUCCESS -> { + isEmpty = newPostList.value?.data?.isEmpty() == true + newPostList.value?.data?.mapIndexed { _, post -> + item { + HomePostView( + post = post, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + } + }, + onClickNickname = { + // todo move to profile } - }, - onClickNickname = { - // todo move to profile - } - ) + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } + + // empty view + if (isEmpty) HomeNewEmptyView() } + // refresh indicator PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } +} + +@Composable +private fun HomeNewEmptyView() { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image(imageVector = ImageVector.vectorResource(id = R.drawable.ic_home_empty), contentDescription = null) + Spacer(modifier = Modifier.height(20.dp)) + + Text(text = stringResource(id = R.string.home_new_empty_title), style = MaterialTheme.typography.b3.copy(Gray3_9FA5AE)) + Spacer(modifier = Modifier.height(2.dp)) + Text(text = stringResource(id = R.string.home_new_empty_detail), style = MaterialTheme.typography.caption1.copy(Gray4_C5CAD2)) + + Spacer(modifier = Modifier.height(28.dp)) + FilledButton(onClick = { /*TODO*/ }, label = stringResource(id = R.string.home_new_empty_action)) + } } \ No newline at end of file From c6e4b73d60c82f3c430d6ef66b9ee921bfed6ea3 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Mon, 12 Feb 2024 12:14:26 +0900 Subject: [PATCH 11/24] #546 [layout] Add Emoji View --- presentation/build.gradle | 9 ++- .../home/HomeDayoPickPostListFragment.kt | 73 +++++++++++++++++-- .../daily/dayo/presentation/view/EmojiView.kt | 34 +++++++++ presentation/src/main/res/values/strings.xml | 1 + 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt diff --git a/presentation/build.gradle b/presentation/build.gradle index fc57e508..2520a257 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -27,7 +27,7 @@ android { versionCode 10000 versionName "1.0.0" - buildConfigField ("String", "NATIVE_APP_KEY", properties['NATIVE_APP_KEY_STR']) + buildConfigField("String", "NATIVE_APP_KEY", properties['NATIVE_APP_KEY_STR']) manifestPlaceholders = [NATIVE_APP_KEY: NATIVE_APP_KEY] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -93,6 +93,7 @@ dependencies { def landscapist_glide_version = "2.2.5" def fragment_version = "1.6.1" def activity_version = "1.7.2" + def emoji_version = "1.0.0-alpha03" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.10.1' @@ -111,7 +112,7 @@ dependencies { // Jetpack Compose // Material implementation("androidx.compose.material3:material3") - implementation ("androidx.compose.material:material-icons-extended:1.5.3") + implementation("androidx.compose.material:material-icons-extended:1.5.3") // Android Studio Preview support implementation("androidx.compose.ui:ui-tooling-preview") debugImplementation("androidx.compose.ui:ui-tooling") @@ -221,6 +222,10 @@ dependencies { // optional - Jetpack Compose integration implementation "androidx.paging:paging-compose:3.2.0" + // emoji + implementation "androidx.emoji2:emoji2:$emoji_version" + implementation "androidx.emoji2:emoji2-views:$emoji_version" + testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index 4d47f7ab..c13815c7 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -5,6 +5,23 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -14,20 +31,20 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager +import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.domain.model.Category +import daily.dayo.domain.model.Post import daily.dayo.presentation.R +import daily.dayo.presentation.activity.MainActivity +import daily.dayo.presentation.adapter.HomeDayoPickAdapter import daily.dayo.presentation.common.GlideLoadUtil.loadImageBackground import daily.dayo.presentation.common.Status import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding -import daily.dayo.presentation.adapter.HomeDayoPickAdapter +import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.viewmodel.HomeViewModel -import com.google.android.material.bottomnavigation.BottomNavigationView -import dagger.hilt.android.AndroidEntryPoint -import daily.dayo.domain.model.Category -import daily.dayo.domain.model.Post -import daily.dayo.presentation.activity.MainActivity import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -52,7 +69,14 @@ class HomeDayoPickPostListFragment : Fragment() { ): View? { binding = FragmentHomeDayoPickPostListBinding.inflate(inflater, container, false) glideRequestManager = Glide.with(this) - return binding.root + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme { + HomeDayoPickScreen() + } + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -83,6 +107,31 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } + @Composable + private fun HomeDayoPickScreen() { + Box(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + ) { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + } + } + private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { loadPosts(homeViewModel.currentDayoPickCategory) @@ -129,8 +178,10 @@ class HomeDayoPickPostListFragment : Fragment() { binding.layoutDayopickPostEmpty.isVisible = postList.isEmpty() } } + Status.LOADING -> { } + Status.ERROR -> { } @@ -285,4 +336,12 @@ class HomeDayoPickPostListFragment : Fragment() { rvDayopickPost.visibility = View.VISIBLE } } + + @Composable + @Preview(showBackground = true) + private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeDayoPickScreen() + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt b/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt new file mode 100644 index 00000000..6066dcb3 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/view/EmojiView.kt @@ -0,0 +1,34 @@ +package daily.dayo.presentation.view + +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color.Companion.Black +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.viewinterop.AndroidView + +@Composable +fun EmojiView( + emoji: String, + emojiSize: TextUnit, + modifier: Modifier = Modifier +) { + AndroidView( + modifier = modifier, + factory = { context -> + AppCompatTextView(context).apply { + setTextColor(Black.toArgb()) + text = emoji + textSize = emojiSize.value + textAlignment = View.TEXT_ALIGNMENT_CENTER + } + }, + update = { + it.apply { + text = emoji + } + }, + ) +} \ 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 8f562b3a..2a3b6027 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ DAYO PICK New + 지금 인기있는 게시글이에요! 인기있는 다꾸가 아직 없어요 다꾸들을 구경하고 반응을 남겨보세요 구경하러 가기 From 1f0d3fc5624f3f0bec409452c546015f81c5692b Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 00:55:30 +0900 Subject: [PATCH 12/24] #546 [layout] add category menu --- .../home/HomeDayoPickPostListFragment.kt | 94 +++++++++++++++++++ .../src/main/res/drawable/ic_category_all.xml | 33 +++++++ .../main/res/drawable/ic_category_digital.xml | 21 +++++ .../src/main/res/drawable/ic_category_etc.xml | 15 +++ .../res/drawable/ic_category_pocketbook.xml | 12 +++ .../res/drawable/ic_category_scheduler.xml | 32 +++++++ .../res/drawable/ic_category_sixholediary.xml | 36 +++++++ .../res/drawable/ic_category_studyplanner.xml | 15 +++ presentation/src/main/res/values/strings.xml | 3 +- 9 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 presentation/src/main/res/drawable/ic_category_all.xml create mode 100644 presentation/src/main/res/drawable/ic_category_digital.xml create mode 100644 presentation/src/main/res/drawable/ic_category_etc.xml create mode 100644 presentation/src/main/res/drawable/ic_category_pocketbook.xml create mode 100644 presentation/src/main/res/drawable/ic_category_scheduler.xml create mode 100644 presentation/src/main/res/drawable/ic_category_sixholediary.xml create mode 100644 presentation/src/main/res/drawable/ic_category_studyplanner.xml diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index c13815c7..1d9b73d8 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -1,25 +1,42 @@ package daily.dayo.presentation.fragment.home +import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.view.isVisible @@ -43,9 +60,12 @@ import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding +import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -107,8 +127,14 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } + @SuppressLint("CoroutineCreationDuringComposition") + @OptIn(ExperimentalMaterialApi::class) @Composable private fun HomeDayoPickScreen() { + var selectedCategory by rememberSaveable { mutableStateOf(Pair(getString(R.string.all), 0)) } + val coroutineScope = rememberCoroutineScope() + val bottomSheetState = getBottomSheetDialogState() + Box(modifier = Modifier.fillMaxSize()) { Row( modifier = Modifier @@ -128,10 +154,78 @@ class HomeDayoPickPostListFragment : Fragment() { .padding(horizontal = 4.dp) .align(Alignment.CenterVertically) ) + + CategoryMenu(selectedCategory.first, coroutineScope, bottomSheetState) } + + BottomSheetDialog( + sheetState = bottomSheetState, + buttons = listOf( + Pair(stringResource(id = R.string.all)) { + selectedCategory = Pair(getString(R.string.all), 0) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.scheduler)) { + selectedCategory = Pair(getString(R.string.scheduler), 1) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.studyplanner)) { + selectedCategory = Pair(getString(R.string.studyplanner), 2) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.pocketbook)) { + selectedCategory = Pair(getString(R.string.pocketbook), 3) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.sixHoleDiary)) { + selectedCategory = Pair(getString(R.string.sixHoleDiary), 4) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.digital)) { + selectedCategory = Pair(getString(R.string.digital), 5) + coroutineScope.launch { bottomSheetState.hide() } + }, + Pair(stringResource(id = R.string.etc)) { + selectedCategory = Pair(getString(R.string.etc), 6) + coroutineScope.launch { bottomSheetState.hide() } + } + ), + title = stringResource(id = R.string.filter), + leftIconButtons = listOf( + ImageVector.vectorResource(R.drawable.ic_category_all), + ImageVector.vectorResource(R.drawable.ic_category_scheduler), + ImageVector.vectorResource(R.drawable.ic_category_studyplanner), + ImageVector.vectorResource(R.drawable.ic_category_pocketbook), + ImageVector.vectorResource(R.drawable.ic_category_sixholediary), + ImageVector.vectorResource(R.drawable.ic_category_digital), + ImageVector.vectorResource(R.drawable.ic_category_etc), + ), + checkedButtonIndex = selectedCategory.second, + closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } + ) } } + @OptIn(ExperimentalMaterialApi::class) + @Composable + private fun CategoryMenu( + selectedCategory: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState + ) { + OutlinedTextField( + value = selectedCategory, + onValueChange = { }, + trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, + readOnly = true, + enabled = false, + modifier = Modifier.clickable( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + indication = null, + interactionSource = remember { MutableInteractionSource() }), + ) + } + private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { loadPosts(homeViewModel.currentDayoPickCategory) diff --git a/presentation/src/main/res/drawable/ic_category_all.xml b/presentation/src/main/res/drawable/ic_category_all.xml new file mode 100644 index 00000000..b5a7c13e --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_all.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_digital.xml b/presentation/src/main/res/drawable/ic_category_digital.xml new file mode 100644 index 00000000..347859d0 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_digital.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_etc.xml b/presentation/src/main/res/drawable/ic_category_etc.xml new file mode 100644 index 00000000..23c6e193 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_etc.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_pocketbook.xml b/presentation/src/main/res/drawable/ic_category_pocketbook.xml new file mode 100644 index 00000000..4c3916f0 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_pocketbook.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_scheduler.xml b/presentation/src/main/res/drawable/ic_category_scheduler.xml new file mode 100644 index 00000000..0f1c1fd6 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_scheduler.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_sixholediary.xml b/presentation/src/main/res/drawable/ic_category_sixholediary.xml new file mode 100644 index 00000000..cf24a826 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_sixholediary.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_studyplanner.xml b/presentation/src/main/res/drawable/ic_category_studyplanner.xml new file mode 100644 index 00000000..7f98c563 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_studyplanner.xml @@ -0,0 +1,15 @@ + + + + + \ 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 2a3b6027..ffd0aff3 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -73,12 +73,13 @@ 공유하러 가기 + 필터 전체 스케줄러 스터디 플래너 포켓북 6공 다이어리 - 굿노트 + 모바일 다이어리 기타 ALL SCHEDULER From 9c49aac6520840ff7f784fdfbad4de3ba3c78dff Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 03:00:16 +0900 Subject: [PATCH 13/24] #546 [layout] Top App Bar in Home Screen --- .../fragment/home/HomeFragment.kt | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt index 5a01c82a..bcd8362b 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt @@ -4,16 +4,42 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R +import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.databinding.FragmentHomeBinding -import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter -import com.google.android.material.tabs.TabLayoutMediator -import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.TextButton +import daily.dayo.presentation.view.TopNavigation const val HOME_DAYOPICK_PAGE_TAB_ID = 0 const val HOME_NEW_PAGE_TAB_ID = 1 @@ -37,7 +63,14 @@ class HomeFragment : Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentHomeBinding.inflate(inflater, container, false) - return binding.root + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme { + HomeScreen() + } + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -50,12 +83,67 @@ class HomeFragment : Fragment() { mediator?.detach() mediator = null pagerAdapter = null - with (binding.pagerHomePost) { + with(binding.pagerHomePost) { unregisterOnPageChangeCallback(pageChangeCallBack) adapter = null } } + @Composable + private fun HomeScreen() { + var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + Scaffold( + topBar = { + TopNavigation( + leftIcon = { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(start = 18.dp) + ) { + TextButton( + onClick = { + homeTabState = HOME_DAYOPICK_PAGE_TAB_ID + }, + text = stringResource(id = R.string.DayoPick), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + + TextButton( + onClick = { + homeTabState = HOME_NEW_PAGE_TAB_ID + }, + text = stringResource(id = R.string.New), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + } + }, + rightIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), + contentDescription = "search", + tint = Gray1_313131, + modifier = Modifier + .padding(end = 12.dp) + .size(24.dp) + ) + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + + } + } + } + private fun setSearchClickListener() { binding.btnPostSearch.setOnDebounceClickListener { findNavController().navigate(R.id.action_homeFragment_to_searchFragment) @@ -92,4 +180,12 @@ class HomeFragment : Fragment() { } mediator?.attach() } + + @Composable + @Preview(showBackground = true) + private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeScreen() + } + } } \ No newline at end of file From 7718623717e39e9c997d40c06e63016084818e17 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 15:58:22 +0900 Subject: [PATCH 14/24] #546 [layout] Main Bottom Navigation --- .../presentation/activity/MainActivity.kt | 158 +++++++++++++++++- .../presentation/screen/home/HomeScreen.kt | 105 ++++++++++++ presentation/src/main/res/values/strings.xml | 2 +- 3 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt 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 c341d73d..af91c6d1 100644 --- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt +++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt @@ -11,22 +11,60 @@ import android.view.MotionEvent import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.BottomNavigation +import androidx.compose.material.BottomNavigationItem +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.view.forEach import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController +import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R import daily.dayo.presentation.databinding.ActivityMainBinding import daily.dayo.presentation.fragment.home.HomeFragmentDirections -import daily.dayo.presentation.viewmodel.SettingNotificationViewModel -import dagger.hilt.android.AndroidEntryPoint +import daily.dayo.presentation.screen.home.HomeScreen +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.AccountViewModel +import daily.dayo.presentation.viewmodel.SettingNotificationViewModel +import kotlinx.coroutines.CoroutineScope @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -36,7 +74,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) + // setContentView(binding.root) setSystemBackClickListener() checkCurrentNotification() initBottomNavigation() @@ -44,6 +82,108 @@ class MainActivity : AppCompatActivity() { disableBottomNaviTooltip() getNotificationData() askNotificationPermission() + setContent { + MainScreen() + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun MainScreen() { + val navController = rememberNavController() + val coroutineScope = rememberCoroutineScope() + val bottomSheetState = getBottomSheetDialogState() + + Scaffold( + bottomBar = { + MainBottomNavigation(navController = navController) + } + ) { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavigationGraph(navController = navController, coroutineScope, bottomSheetState) + } + } + } + + @Composable + fun MainBottomNavigation(navController: NavController) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + val items = listOf( + Screen.Home, + Screen.Feed, + Screen.Write, + Screen.Notification, + Screen.MyPage + ) + + BottomNavigation( + backgroundColor = White_FFFFFF, + contentColor = Gray2_767B83, + modifier = Modifier.height(73.dp) + ) { + items.forEach { screen -> + val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true + BottomNavigationItem( + icon = { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = ImageVector.vectorResource(id = if (selected) screen.selectedIcon else screen.defaultIcon), + contentDescription = stringResource(id = screen.resourceId), + modifier = Modifier + .size(if (screen.route != Screen.Write.route) 24.dp else 36.dp) + ) + + if (screen.route != Screen.Write.route) { + Text(text = stringResource(screen.resourceId), style = MaterialTheme.typography.caption) + } + } + }, + selected = selected, + selectedContentColor = Gray1_313131, + onClick = { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun NavigationGraph( + navController: NavHostController, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState + ) { + NavHost( + navController = navController, + startDestination = Screen.Home.route + ) { + composable(Screen.Home.route) { + HomeScreen(navController, coroutineScope, bottomSheetState) + } + composable(Screen.Feed.route) { + + } + composable(Screen.Write.route) { + + } + composable(Screen.Notification.route) { + + } + composable(Screen.MyPage.route) { + + } + } } private fun setSystemBackClickListener() { @@ -137,6 +277,7 @@ class MainActivity : AppCompatActivity() { ).show() } } + else -> { //All request are permitted // 알림 최초 허용시에 모든 알림 허용처리 @@ -204,12 +345,14 @@ class MainActivity : AppCompatActivity() { isInside = true return true } + MotionEvent.ACTION_MOVE -> { isInside = rect.contains(v!!.left + event.x.toInt(), v.top + event.y.toInt()) binding.bottomNavigationMainBar.clearFocus() return false } + MotionEvent.ACTION_UP -> { binding.bottomNavigationMainBar.menu.findItem(R.id.WriteFragment) .setIcon(R.drawable.ic_write) @@ -223,6 +366,7 @@ class MainActivity : AppCompatActivity() { } return true } + else -> return true } } @@ -249,4 +393,12 @@ class MainActivity : AppCompatActivity() { companion object { val notificationPermission = arrayOf(Manifest.permission.POST_NOTIFICATIONS) } + + sealed class Screen(val route: String, @StringRes val resourceId: Int, @DrawableRes val defaultIcon: Int, @DrawableRes val selectedIcon: Int) { + object Home : Screen("home", R.string.home, R.drawable.ic_home, R.drawable.ic_home_filled) + object Feed : Screen("feed", R.string.feed, R.drawable.ic_feed, R.drawable.ic_feed_filled) + object Write : Screen("write", R.string.write, R.drawable.ic_write, R.drawable.ic_write_filled) + object Notification : Screen("notification", R.string.notification, R.drawable.ic_notification, R.drawable.ic_notification_filled) + object MyPage : Screen("mypage", R.string.my_page, R.drawable.ic_my_page, R.drawable.ic_my_page_filled) + } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt new file mode 100644 index 00000000..8e6edab2 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -0,0 +1,105 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import daily.dayo.presentation.R +import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID +import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.TextButton +import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.view.getBottomSheetDialogState +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeScreen( + navController: NavController, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + Scaffold( + topBar = { + TopNavigation( + leftIcon = { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(start = 18.dp) + ) { + TextButton( + onClick = { + homeTabState = HOME_DAYOPICK_PAGE_TAB_ID + }, + text = stringResource(id = R.string.DayoPick), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + + TextButton( + onClick = { + homeTabState = HOME_NEW_PAGE_TAB_ID + }, + text = stringResource(id = R.string.New), + textStyle = MaterialTheme.typography.titleLarge.copy( + color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, + fontWeight = FontWeight.ExtraBold + ) + ) + } + }, + rightIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), + contentDescription = "search", + tint = Gray1_313131, + modifier = Modifier + .padding(end = 12.dp) + .size(24.dp) + ) + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +@Preview(showBackground = true) +private fun PreviewHomeScreen() { + MaterialTheme { + HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState()) + } +} \ 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 ffd0aff3..12da9efb 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -6,7 +6,7 @@ 피드 글쓰기 알림 - 마이 페이지 + MY 다음 확인 완료 From c7d05558c822dfdce1a3fd5da902d4f36ee5633c Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 18:46:10 +0900 Subject: [PATCH 15/24] #546 [layout] Set bottom sheet dialog to appear above bottom navigation bar --- .../presentation/activity/MainActivity.kt | 31 ++++-- .../screen/home/HomeDayoPickScreen.kt | 94 +++++++++++++++++++ .../presentation/screen/home/HomeScreen.kt | 68 +++++++++++++- 3 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt 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 af91c6d1..0c01ff5d 100644 --- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt +++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt @@ -1,6 +1,7 @@ package daily.dayo.presentation.activity import android.Manifest +import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.graphics.Rect @@ -32,7 +33,10 @@ import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.runtime.Composable 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -87,20 +91,30 @@ class MainActivity : AppCompatActivity() { } } + @SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalMaterialApi::class) @Composable fun MainScreen() { val navController = rememberNavController() val coroutineScope = rememberCoroutineScope() val bottomSheetState = getBottomSheetDialogState() + var bottomSheet: (@Composable () -> Unit)? by remember { mutableStateOf(null) } + val bottomSheetContent: (@Composable () -> Unit) -> Unit = { + bottomSheet = it + } Scaffold( - bottomBar = { - MainBottomNavigation(navController = navController) - } - ) { innerPadding -> - Box(Modifier.padding(innerPadding)) { - NavigationGraph(navController = navController, coroutineScope, bottomSheetState) + bottomBar = { bottomSheet?.let { it() } } + ) { + Scaffold( + bottomBar = { + MainBottomNavigation(navController = navController) + + } + ) { innerPadding -> + Box(Modifier.padding(innerPadding)) { + NavigationGraph(navController = navController, coroutineScope, bottomSheetState, bottomSheetContent) + } } } } @@ -162,14 +176,15 @@ class MainActivity : AppCompatActivity() { fun NavigationGraph( navController: NavHostController, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + bottomSheetContent: (@Composable () -> Unit) -> Unit ) { NavHost( navController = navController, startDestination = Screen.Home.route ) { composable(Screen.Home.route) { - HomeScreen(navController, coroutineScope, bottomSheetState) + HomeScreen(navController, coroutineScope, bottomSheetState, bottomSheetContent) } composable(Screen.Feed.route) { diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt new file mode 100644 index 00000000..b13239c3 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -0,0 +1,94 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import daily.dayo.presentation.R +import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.getBottomSheetDialogState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeDayoPickScreen( + selectedCategoryName: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + Box(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + ) { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun CategoryButton( + selectedCategory: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + OutlinedTextField( + value = selectedCategory, + onValueChange = { }, + trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, + readOnly = true, + enabled = false, + modifier = Modifier.clickable( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + indication = null, + interactionSource = remember { MutableInteractionSource() }), + ) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +@Preview(showBackground = true) +private fun PreviewHomeDayoPickScreen() { + MaterialTheme { + HomeDayoPickScreen(CategoryMenu.All.name, rememberCoroutineScope(), getBottomSheetDialogState()) + } +} + diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 8e6edab2..4129d061 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -1,5 +1,6 @@ package daily.dayo.presentation.screen.home +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -13,6 +14,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -30,19 +32,28 @@ import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID import daily.dayo.presentation.theme.Gray1_313131 import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.TextButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.getBottomSheetDialogState import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @Composable fun HomeScreen( navController: NavController, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + bottomSheetContent: (@Composable () -> Unit) -> Unit ) { var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } + var selectedCategory by rememberSaveable { mutableStateOf(Pair(CategoryMenu.All.name, 0)) } // name, index + val onCategorySelected: (String, Int) -> Unit = { category, index -> + selectedCategory = Pair(category, index) + coroutineScope.launch { bottomSheetState.hide() } + } + Scaffold( topBar = { TopNavigation( @@ -90,9 +101,47 @@ fun HomeScreen( Column( modifier = Modifier.padding(innerPadding) ) { - + HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState) } } + + bottomSheetContent { + CategoryBottomSheetDialog(onCategorySelected, selectedCategory, coroutineScope, bottomSheetState) + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun CategoryBottomSheetDialog( + onCategorySelected: (String, Int) -> Unit, + selectedCategory: Pair, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState +) { + val categoryMenus = listOf( + CategoryMenu.All, + CategoryMenu.Scheduler, + CategoryMenu.StudyPlanner, + CategoryMenu.PocketBook, + CategoryMenu.SixHoleDiary, + CategoryMenu.Digital, + CategoryMenu.ETC + ) + + BottomSheetDialog( + sheetState = bottomSheetState, + buttons = categoryMenus.mapIndexed { index, category -> + Pair(category.name) { + onCategorySelected(category.name, index) + } + }, + title = stringResource(id = R.string.filter), + leftIconButtons = categoryMenus.map { + ImageVector.vectorResource(it.defaultIcon) + }, + checkedButtonIndex = selectedCategory.second, + closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } + ) } @OptIn(ExperimentalMaterialApi::class) @@ -100,6 +149,17 @@ fun HomeScreen( @Preview(showBackground = true) private fun PreviewHomeScreen() { MaterialTheme { - HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState()) + HomeScreen(rememberNavController(), rememberCoroutineScope(), getBottomSheetDialogState(), {}) } -} \ No newline at end of file +} + + +sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int) { + object All : CategoryMenu("전체", R.drawable.ic_category_all) + object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler) + object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner) + object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook) + object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary) + object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital) + object ETC : CategoryMenu("기타", R.drawable.ic_category_etc) +} From dc2af25bfcad9034e4491ba8dae2c6ad2268667f Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 21 Feb 2024 19:27:42 +0900 Subject: [PATCH 16/24] #546 [layout] dayo pick description placement --- .../screen/home/HomeDayoPickScreen.kt | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index b13239c3..ea6fde03 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -1,23 +1,26 @@ package daily.dayo.presentation.screen.home -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -26,6 +29,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import daily.dayo.presentation.R +import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.Gray6_F0F1F3 +import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.view.getBottomSheetDialogState import kotlinx.coroutines.CoroutineScope @@ -38,26 +45,35 @@ fun HomeDayoPickScreen( coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState ) { - Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 18.dp) + ) { + // description Row( modifier = Modifier .wrapContentHeight() .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } } @@ -70,17 +86,18 @@ private fun CategoryButton( coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState ) { - OutlinedTextField( - value = selectedCategory, - onValueChange = { }, - trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, - readOnly = true, - enabled = false, - modifier = Modifier.clickable( - onClick = { coroutineScope.launch { bottomSheetState.show() } }, - indication = null, - interactionSource = remember { MutableInteractionSource() }), - ) + Button( + onClick = { coroutineScope.launch { bottomSheetState.show() } }, + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(top = 6.dp, bottom = 6.dp, start = 12.dp, end = 4.dp), + colors = androidx.compose.material3.ButtonDefaults.buttonColors(containerColor = White_FFFFFF, contentColor = Gray2_767B83), + modifier = Modifier + .border(1.dp, Gray6_F0F1F3, shape = RoundedCornerShape(8.dp)) + ) { + Text(text = selectedCategory, style = MaterialTheme.typography.caption3) + Spacer(modifier = Modifier.width(8.dp)) + Icon(Icons.Filled.ArrowDropDown, "category menu") + } } @OptIn(ExperimentalMaterialApi::class) From 2df8512f1c06d5f2e1a3eb0081b0b98d7748b572 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Thu, 22 Feb 2024 00:36:15 +0900 Subject: [PATCH 17/24] #546 [layout] Show Dayo Pick Post List --- presentation/build.gradle | 6 +- .../screen/home/HomeDayoPickScreen.kt | 102 +++++++++++++----- .../dayo/presentation/view/HomePostView.kt | 34 ++++++ 3 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt diff --git a/presentation/build.gradle b/presentation/build.gradle index 2520a257..0fa5be7e 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -140,7 +140,8 @@ dependencies { // Testing Navigation androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" // Jetpack Compose Integration - implementation("androidx.navigation:navigation-compose:$nav_version") + implementation "androidx.navigation:navigation-compose:$nav_version" + implementation "androidx.hilt:hilt-navigation-compose:1.1.0" // ViewPager2 implementation "androidx.viewpager2:viewpager2:1.0.0" @@ -199,6 +200,9 @@ dependencies { // Glide-For-Compose implementation "com.github.skydoves:landscapist-glide:$landscapist_glide_version" + // Coil + implementation "io.coil-kt:coil-compose:2.5.0" + // Preference implementation 'androidx.preference:preference-ktx:1.1.1' diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index ea6fde03..d241e365 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -2,15 +2,19 @@ package daily.dayo.presentation.screen.home import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState @@ -21,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -28,13 +33,18 @@ import androidx.compose.ui.graphics.Color 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 daily.dayo.domain.model.Category import daily.dayo.presentation.R +import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 import daily.dayo.presentation.theme.Gray6_F0F1F3 import daily.dayo.presentation.theme.White_FFFFFF import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.view.getBottomSheetDialogState +import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -43,42 +53,81 @@ import kotlinx.coroutines.launch fun HomeDayoPickScreen( selectedCategoryName: String, coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState + bottomSheetState: ModalBottomSheetState, + homeViewModel: HomeViewModel = hiltViewModel() ) { - Column( + val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() + loadPosts(homeViewModel, Category.ALL) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), modifier = Modifier .fillMaxSize() - .padding(horizontal = 18.dp) ) { // description - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + + item(span = { GridItemSpan(2) }) { + Spacer(modifier = Modifier.size(12.dp)) + } + + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + dayoPickPostList.value?.data?.let { + items(it) { post -> + HomePostView(post) + } + } + } + + Status.LOADING -> { + } + + Status.ERROR -> { + + } + + else -> {} } } } +private fun loadPosts(homeViewModel: HomeViewModel, selectCategory: Category) { + with(homeViewModel) { + currentDayoPickCategory = selectCategory + requestDayoPickPostList() + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable private fun CategoryButton( @@ -109,3 +158,4 @@ private fun PreviewHomeDayoPickScreen() { } } + diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt new file mode 100644 index 00000000..52a495c1 --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -0,0 +1,34 @@ +package daily.dayo.presentation.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import daily.dayo.domain.model.Post +import daily.dayo.presentation.BuildConfig + +@Composable +fun HomePostView(post: Post, modifier: Modifier = Modifier) { + Column( + modifier = modifier.padding(bottom = 10.dp) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") + .build(), + contentDescription = "dayo pick image", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(size = 8.dp)) + ) + } +} \ No newline at end of file From 0a1e9f226c794af161fecf3d830aa880c713ef25 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 28 Feb 2024 18:20:09 +0900 Subject: [PATCH 18/24] #546 [layout] Implementing a HomePostView --- .../screen/home/HomeDayoPickScreen.kt | 14 +- .../dayo/presentation/view/HomePostView.kt | 152 ++++++++++++++++-- .../src/main/res/drawable/ic_dayo_pick.xml | 21 +++ 3 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 presentation/src/main/res/drawable/ic_dayo_pick.xml diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index d241e365..68695746 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState @@ -72,7 +70,7 @@ fun HomeDayoPickScreen( modifier = Modifier .wrapContentHeight() .fillMaxWidth() - .padding(top = 8.dp), + .padding(top = 8.dp, bottom = 12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -95,16 +93,12 @@ fun HomeDayoPickScreen( } } - item(span = { GridItemSpan(2) }) { - Spacer(modifier = Modifier.size(12.dp)) - } - // dayo pick list when (dayoPickPostList.value?.status) { Status.SUCCESS -> { - dayoPickPostList.value?.data?.let { - items(it) { post -> - HomePostView(post) + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView(post = post, isDayoPick = index in 0..4, modifier = Modifier.padding(bottom = 20.dp)) } } } diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt index 52a495c1..3f0524be 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -1,34 +1,158 @@ package daily.dayo.presentation.view +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale 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 import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest import daily.dayo.domain.model.Post import daily.dayo.presentation.BuildConfig +import daily.dayo.presentation.R +import daily.dayo.presentation.common.extension.clickableSingle +import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.b5 +import daily.dayo.presentation.theme.caption3 +import java.text.DecimalFormat @Composable -fun HomePostView(post: Post, modifier: Modifier = Modifier) { - Column( - modifier = modifier.padding(bottom = 10.dp) - ) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") - .build(), - contentDescription = "dayo pick image", - contentScale = ContentScale.Crop, +fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean = false) { + val imageInteractionSource = remember { MutableInteractionSource() } + Column(modifier = modifier) { + Box( modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(size = 8.dp)) + .fillMaxWidth() + .aspectRatio(1f) + ) { + // Thumbnail Image + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") + .build(), + contentDescription = "dayo pick image", + contentScale = ContentScale.Crop, + modifier = Modifier + .matchParentSize() + .clip(RoundedCornerShape(size = 8.dp)) + .clickableSingle( + interactionSource = imageInteractionSource, + indication = null, + onClick = { } + ) + ) + + // Dayo Pick Icon + if (isDayoPick) { + Image( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_dayo_pick), + modifier = Modifier + .align(Alignment.TopEnd) + .padding(end = 12.dp), + contentDescription = null + ) + } + + // Like Button + Image( + imageVector = ImageVector.vectorResource(id = if (post.heart) R.drawable.ic_heart_filled else R.drawable.ic_heart), + modifier = Modifier + .padding(bottom = 12.dp, end = 11.dp) + .align(Alignment.BottomEnd) + .clickable { }, + contentDescription = "like Button", + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Publisher Info + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.wrapContentHeight() + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("${BuildConfig.BASE_URL}/images/${post.userProfileImage}") + .build(), + contentDescription = "${post.nickname} + profile", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(16.dp) + .clip(shape = CircleShape) + .align(Alignment.CenterVertically) + .clickableSingle( + interactionSource = imageInteractionSource, + indication = null, + onClick = { } + ) + ) + Text(text = post.nickname, style = MaterialTheme.typography.b5.copy(Gray1_313131)) + } + + Spacer(modifier = Modifier.height(2.dp)) + + // Post Info + val dec = DecimalFormat("#,###") + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Text(text = stringResource(id = R.string.like) + " ${dec.format(post.heartCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) + Text(text = stringResource(id = R.string.comment) + " ${dec.format(post.commentCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) + } + } +} + +@Composable +@Preview(showBackground = true) +private fun PreviewHomePostView() { + MaterialTheme { + HomePostView( + modifier = Modifier.size(156.dp, 205.dp), + post = Post( + postId = 0, + thumbnailImage = "", + memberId = "", + nickname = "nickname", + userProfileImage = "", + heartCount = 123456, + commentCount = 8100, + heart = true, + category = null, + postImages = null, + contents = null, + createDateTime = null, + folderId = null, + folderName = null, + comments = null, + hashtags = null, + bookmark = null + ), + isDayoPick = true ) } -} \ No newline at end of file +} diff --git a/presentation/src/main/res/drawable/ic_dayo_pick.xml b/presentation/src/main/res/drawable/ic_dayo_pick.xml new file mode 100644 index 00000000..75510533 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_dayo_pick.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file From 3ba1fd686fa430bff5e78c610467f8cb29eee785 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Thu, 29 Feb 2024 01:27:17 +0900 Subject: [PATCH 19/24] #546 [feature] Add HomeNewScreen --- .../home/HomeDayoPickPostListFragment.kt | 166 +----------------- .../fragment/home/HomeFragment.kt | 106 +---------- .../fragment/home/HomeNewPostListFragment.kt | 12 +- .../screen/home/HomeDayoPickScreen.kt | 141 ++++++++------- .../presentation/screen/home/HomeNewScreen.kt | 126 +++++++++++++ .../presentation/screen/home/HomeScreen.kt | 50 ++++-- .../dayo/presentation/view/HomePostView.kt | 47 +++-- .../presentation/viewmodel/HomeViewModel.kt | 66 +++++-- presentation/src/main/res/values/strings.xml | 1 + 9 files changed, 341 insertions(+), 374 deletions(-) create mode 100644 presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt index 1d9b73d8..589c312c 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeDayoPickPostListFragment.kt @@ -1,44 +1,10 @@ package daily.dayo.presentation.fragment.home -import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -60,12 +26,8 @@ import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.common.toByteArray import daily.dayo.presentation.databinding.FragmentHomeDayoPickPostListBinding -import daily.dayo.presentation.view.BottomSheetDialog -import daily.dayo.presentation.view.EmojiView -import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -79,8 +41,10 @@ class HomeDayoPickPostListFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + /* if (savedInstanceState == null) loadPosts(homeViewModel.currentDayoPickCategory) + */ } override fun onCreateView( @@ -89,14 +53,7 @@ class HomeDayoPickPostListFragment : Fragment() { ): View? { binding = FragmentHomeDayoPickPostListBinding.inflate(inflater, container, false) glideRequestManager = Glide.with(this) - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme { - HomeDayoPickScreen() - } - } - } + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -127,112 +84,14 @@ class HomeDayoPickPostListFragment : Fragment() { binding.rvDayopickPost.adapter = null } - @SuppressLint("CoroutineCreationDuringComposition") - @OptIn(ExperimentalMaterialApi::class) - @Composable - private fun HomeDayoPickScreen() { - var selectedCategory by rememberSaveable { mutableStateOf(Pair(getString(R.string.all), 0)) } - val coroutineScope = rememberCoroutineScope() - val bottomSheetState = getBottomSheetDialogState() - - Box(modifier = Modifier.fillMaxSize()) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - ) { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) - - CategoryMenu(selectedCategory.first, coroutineScope, bottomSheetState) - } - - BottomSheetDialog( - sheetState = bottomSheetState, - buttons = listOf( - Pair(stringResource(id = R.string.all)) { - selectedCategory = Pair(getString(R.string.all), 0) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.scheduler)) { - selectedCategory = Pair(getString(R.string.scheduler), 1) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.studyplanner)) { - selectedCategory = Pair(getString(R.string.studyplanner), 2) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.pocketbook)) { - selectedCategory = Pair(getString(R.string.pocketbook), 3) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.sixHoleDiary)) { - selectedCategory = Pair(getString(R.string.sixHoleDiary), 4) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.digital)) { - selectedCategory = Pair(getString(R.string.digital), 5) - coroutineScope.launch { bottomSheetState.hide() } - }, - Pair(stringResource(id = R.string.etc)) { - selectedCategory = Pair(getString(R.string.etc), 6) - coroutineScope.launch { bottomSheetState.hide() } - } - ), - title = stringResource(id = R.string.filter), - leftIconButtons = listOf( - ImageVector.vectorResource(R.drawable.ic_category_all), - ImageVector.vectorResource(R.drawable.ic_category_scheduler), - ImageVector.vectorResource(R.drawable.ic_category_studyplanner), - ImageVector.vectorResource(R.drawable.ic_category_pocketbook), - ImageVector.vectorResource(R.drawable.ic_category_sixholediary), - ImageVector.vectorResource(R.drawable.ic_category_digital), - ImageVector.vectorResource(R.drawable.ic_category_etc), - ), - checkedButtonIndex = selectedCategory.second, - closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } - ) - } - } - - @OptIn(ExperimentalMaterialApi::class) - @Composable - private fun CategoryMenu( - selectedCategory: String, - coroutineScope: CoroutineScope, - bottomSheetState: ModalBottomSheetState - ) { - OutlinedTextField( - value = selectedCategory, - onValueChange = { }, - trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "category menu") }, - readOnly = true, - enabled = false, - modifier = Modifier.clickable( - onClick = { coroutineScope.launch { bottomSheetState.show() } }, - indication = null, - interactionSource = remember { MutableInteractionSource() }), - ) - } - private fun setDayoPickPostListRefreshListener() { binding.swipeRefreshLayoutDayoPickPost.setOnRefreshListener { - loadPosts(homeViewModel.currentDayoPickCategory) + // loadPosts(homeViewModel.currentDayoPickCategory) } } private fun setInitialCategory() { + /* with(binding) { radiogroupDayopickPostCategory.check( when (homeViewModel.currentDayoPickCategory) { @@ -246,6 +105,7 @@ class HomeDayoPickPostListFragment : Fragment() { } ) } + */ } private fun setRvDayoPickPostAdapter() { @@ -309,7 +169,7 @@ class HomeDayoPickPostListFragment : Fragment() { private fun loadPosts(selectCategory: Category, isSmoothScroll: Boolean = false) { with(homeViewModel) { - currentDayoPickCategory = selectCategory + // currentDayoPickCategory = selectCategory requestDayoPickPostList() } @@ -335,7 +195,7 @@ class HomeDayoPickPostListFragment : Fragment() { this@HomeDayoPickPostListFragment.tag, "PostId Null Exception Occurred" ) - loadPosts(homeViewModel.currentDayoPickCategory) + // loadPosts(homeViewModel.currentDayoPickCategory) } } } @@ -406,7 +266,7 @@ class HomeDayoPickPostListFragment : Fragment() { (requireActivity() as MainActivity).setBottomNavigationIconClickListener(reselectedIconId = R.id.HomeFragment) { if (currentViewPagerPosition == HOME_DAYOPICK_PAGE_TAB_ID) { binding.swipeRefreshLayoutDayoPickPost.isRefreshing = true - loadPosts(homeViewModel.currentDayoPickCategory, isSmoothScroll = true) + // loadPosts(homeViewModel.currentDayoPickCategory, isSmoothScroll = true) } } } @@ -430,12 +290,4 @@ class HomeDayoPickPostListFragment : Fragment() { rvDayopickPost.visibility = View.VISIBLE } } - - @Composable - @Preview(showBackground = true) - private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeDayoPickScreen() - } - } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt index bcd8362b..5a01c82a 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeFragment.kt @@ -4,42 +4,16 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.tabs.TabLayoutMediator -import dagger.hilt.android.AndroidEntryPoint import daily.dayo.presentation.R -import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter import daily.dayo.presentation.common.autoCleared import daily.dayo.presentation.common.setOnDebounceClickListener import daily.dayo.presentation.databinding.FragmentHomeBinding -import daily.dayo.presentation.theme.Gray1_313131 -import daily.dayo.presentation.theme.Gray5_E8EAEE -import daily.dayo.presentation.view.TextButton -import daily.dayo.presentation.view.TopNavigation +import daily.dayo.presentation.adapter.HomeFragmentPagerStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint const val HOME_DAYOPICK_PAGE_TAB_ID = 0 const val HOME_NEW_PAGE_TAB_ID = 1 @@ -63,14 +37,7 @@ class HomeFragment : Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentHomeBinding.inflate(inflater, container, false) - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme { - HomeScreen() - } - } - } + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -83,67 +50,12 @@ class HomeFragment : Fragment() { mediator?.detach() mediator = null pagerAdapter = null - with(binding.pagerHomePost) { + with (binding.pagerHomePost) { unregisterOnPageChangeCallback(pageChangeCallBack) adapter = null } } - @Composable - private fun HomeScreen() { - var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } - Scaffold( - topBar = { - TopNavigation( - leftIcon = { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier.padding(start = 18.dp) - ) { - TextButton( - onClick = { - homeTabState = HOME_DAYOPICK_PAGE_TAB_ID - }, - text = stringResource(id = R.string.DayoPick), - textStyle = MaterialTheme.typography.titleLarge.copy( - color = if (homeTabState == HOME_DAYOPICK_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, - fontWeight = FontWeight.ExtraBold - ) - ) - - TextButton( - onClick = { - homeTabState = HOME_NEW_PAGE_TAB_ID - }, - text = stringResource(id = R.string.New), - textStyle = MaterialTheme.typography.titleLarge.copy( - color = if (homeTabState == HOME_NEW_PAGE_TAB_ID) Gray1_313131 else Gray5_E8EAEE, - fontWeight = FontWeight.ExtraBold - ) - ) - } - }, - rightIcon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), - contentDescription = "search", - tint = Gray1_313131, - modifier = Modifier - .padding(end = 12.dp) - .size(24.dp) - ) - } - ) - } - ) { innerPadding -> - Column( - modifier = Modifier.padding(innerPadding) - ) { - - } - } - } - private fun setSearchClickListener() { binding.btnPostSearch.setOnDebounceClickListener { findNavController().navigate(R.id.action_homeFragment_to_searchFragment) @@ -180,12 +92,4 @@ class HomeFragment : Fragment() { } mediator?.attach() } - - @Composable - @Preview(showBackground = true) - private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeScreen() - } - } } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt index ceefafd8..0fab6996 100644 --- a/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt +++ b/presentation/src/main/java/daily/dayo/presentation/fragment/home/HomeNewPostListFragment.kt @@ -44,8 +44,10 @@ class HomeNewPostListFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + /* if (savedInstanceState == null) loadPosts(homeViewModel.currentNewCategory) + */ } override fun onCreateView( @@ -87,11 +89,12 @@ class HomeNewPostListFragment : Fragment() { private fun setNewPostListRefreshListener() { binding.swipeRefreshLayoutNewPost.setOnRefreshListener { - loadPosts(homeViewModel.currentNewCategory) + // loadPosts(homeViewModel.currentNewCategory) } } private fun setInitialCategory() { + /* with(binding) { radiogroupNewPostCategory.check( when (homeViewModel.currentNewCategory) { @@ -105,6 +108,7 @@ class HomeNewPostListFragment : Fragment() { } ) } + */ } private fun setRvNewPostAdapter() { @@ -164,7 +168,7 @@ class HomeNewPostListFragment : Fragment() { private fun loadPosts(selectCategory: Category, isSmoothScroll: Boolean = false) { with(homeViewModel) { - currentNewCategory = selectCategory + // currentNewCategory = selectCategory requestNewPostList() } @@ -187,7 +191,7 @@ class HomeNewPostListFragment : Fragment() { } } catch (postIdNullException: NullPointerException) { Log.e(this@HomeNewPostListFragment.tag, "PostId Null Exception Occurred") - loadPosts(homeViewModel.currentNewCategory) + // loadPosts(homeViewModel.currentNewCategory) } } } @@ -253,7 +257,7 @@ class HomeNewPostListFragment : Fragment() { (requireActivity() as MainActivity).setBottomNavigationIconClickListener(reselectedIconId = R.id.HomeFragment) { if (currentViewPagerPosition == HOME_NEW_PAGE_TAB_ID) { binding.swipeRefreshLayoutNewPost.isRefreshing = true - loadPosts(homeViewModel.currentNewCategory, isSmoothScroll = true) + // loadPosts(homeViewModel.currentNewCategory, isSmoothScroll = true) } } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index 68695746..0e601543 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -2,6 +2,7 @@ package daily.dayo.presentation.screen.home import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,21 +19,23 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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 daily.dayo.domain.model.Category +import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 @@ -41,7 +44,6 @@ import daily.dayo.presentation.theme.White_FFFFFF import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView import daily.dayo.presentation.view.HomePostView -import daily.dayo.presentation.view.getBottomSheetDialogState import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -55,76 +57,91 @@ fun HomeDayoPickScreen( homeViewModel: HomeViewModel = hiltViewModel() ) { val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() - loadPosts(homeViewModel, Category.ALL) + val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() + val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadDayoPickPosts() }) + Box(Modifier.pullRefresh(pullRefreshState)) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // dayo pick list - when (dayoPickPostList.value?.status) { - Status.SUCCESS -> { - dayoPickPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView(post = post, isDayoPick = index in 0..4, modifier = Modifier.padding(bottom = 20.dp)) + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + isDayoPick = index in 0..4, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + } + }, + onClickNickname = { + // todo move to profile + } + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } - } -} -private fun loadPosts(homeViewModel: HomeViewModel, selectCategory: Category) { - with(homeViewModel) { - currentDayoPickCategory = selectCategory - requestDayoPickPostList() + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } } @OptIn(ExperimentalMaterialApi::class) @Composable -private fun CategoryButton( +fun CategoryButton( selectedCategory: String, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState @@ -143,13 +160,3 @@ private fun CategoryButton( } } -@OptIn(ExperimentalMaterialApi::class) -@Composable -@Preview(showBackground = true) -private fun PreviewHomeDayoPickScreen() { - MaterialTheme { - HomeDayoPickScreen(CategoryMenu.All.name, rememberCoroutineScope(), getBottomSheetDialogState()) - } -} - - diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt new file mode 100644 index 00000000..f6d9219f --- /dev/null +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt @@ -0,0 +1,126 @@ +package daily.dayo.presentation.screen.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import daily.dayo.presentation.R +import daily.dayo.presentation.common.Status +import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.HomePostView +import daily.dayo.presentation.viewmodel.HomeViewModel +import kotlinx.coroutines.CoroutineScope + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun HomeNewScreen( + selectedCategoryName: String, + coroutineScope: CoroutineScope, + bottomSheetState: ModalBottomSheetState, + homeViewModel: HomeViewModel = hiltViewModel() +) { + val newPostList = homeViewModel.newPostList.observeAsState() + val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() + val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadNewPosts() }) + Box(Modifier.pullRefresh(pullRefreshState)) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDC40", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_new_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) + } + } + + // new post list + when (newPostList.value?.status) { + Status.SUCCESS -> { + newPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + } + }, + onClickNickname = { + // todo move to profile + } + ) + } + } + } + + Status.LOADING -> { + } + + Status.ERROR -> { + + } + + else -> {} + } + } + + PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 4129d061..055e19fb 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -25,32 +25,38 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import daily.dayo.domain.model.Category import daily.dayo.presentation.R -import daily.dayo.presentation.fragment.home.HOME_DAYOPICK_PAGE_TAB_ID -import daily.dayo.presentation.fragment.home.HOME_NEW_PAGE_TAB_ID import daily.dayo.presentation.theme.Gray1_313131 import daily.dayo.presentation.theme.Gray5_E8EAEE import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.TextButton import daily.dayo.presentation.view.TopNavigation import daily.dayo.presentation.view.getBottomSheetDialogState +import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +const val HOME_DAYOPICK_PAGE_TAB_ID = 0 +const val HOME_NEW_PAGE_TAB_ID = 1 + @OptIn(ExperimentalMaterialApi::class) @Composable fun HomeScreen( navController: NavController, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState, - bottomSheetContent: (@Composable () -> Unit) -> Unit + bottomSheetContent: (@Composable () -> Unit) -> Unit, + homeViewModel: HomeViewModel = hiltViewModel() ) { var homeTabState by rememberSaveable { mutableIntStateOf(HOME_DAYOPICK_PAGE_TAB_ID) } var selectedCategory by rememberSaveable { mutableStateOf(Pair(CategoryMenu.All.name, 0)) } // name, index - val onCategorySelected: (String, Int) -> Unit = { category, index -> - selectedCategory = Pair(category, index) + val onClickCategory: (CategoryMenu, Int) -> Unit = { categoryMenu, index -> + selectedCategory = Pair(categoryMenu.name, index) + homeViewModel.setCategory(categoryMenu.category) coroutineScope.launch { bottomSheetState.hide() } } @@ -101,19 +107,29 @@ fun HomeScreen( Column( modifier = Modifier.padding(innerPadding) ) { - HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState) + when (homeTabState) { + HOME_DAYOPICK_PAGE_TAB_ID -> { + HomeDayoPickScreen(selectedCategory.first, coroutineScope, bottomSheetState, homeViewModel) + homeViewModel.loadDayoPickPosts() + } + + HOME_NEW_PAGE_TAB_ID -> { + HomeNewScreen(selectedCategory.first, coroutineScope, bottomSheetState, homeViewModel) + homeViewModel.loadNewPosts() + } + } } } bottomSheetContent { - CategoryBottomSheetDialog(onCategorySelected, selectedCategory, coroutineScope, bottomSheetState) + CategoryBottomSheetDialog(onClickCategory, selectedCategory, coroutineScope, bottomSheetState) } } @OptIn(ExperimentalMaterialApi::class) @Composable private fun CategoryBottomSheetDialog( - onCategorySelected: (String, Int) -> Unit, + onCategorySelected: (CategoryMenu, Int) -> Unit, selectedCategory: Pair, coroutineScope: CoroutineScope, bottomSheetState: ModalBottomSheetState @@ -132,7 +148,7 @@ private fun CategoryBottomSheetDialog( sheetState = bottomSheetState, buttons = categoryMenus.mapIndexed { index, category -> Pair(category.name) { - onCategorySelected(category.name, index) + onCategorySelected(category, index) } }, title = stringResource(id = R.string.filter), @@ -154,12 +170,12 @@ private fun PreviewHomeScreen() { } -sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int) { - object All : CategoryMenu("전체", R.drawable.ic_category_all) - object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler) - object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner) - object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook) - object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary) - object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital) - object ETC : CategoryMenu("기타", R.drawable.ic_category_etc) +sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int, val category: Category) { + object All : CategoryMenu("전체", R.drawable.ic_category_all, Category.ALL) + object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler, Category.SCHEDULER) + object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner, Category.STUDY_PLANNER) + object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook, Category.POCKET_BOOK) + object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary, Category.SIX_DIARY) + object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital, Category.GOOD_NOTE) + object ETC : CategoryMenu("기타", R.drawable.ic_category_etc, Category.STUDY_PLANNER) } diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt index 3f0524be..34a7aaae 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt @@ -1,7 +1,6 @@ package daily.dayo.presentation.view import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -16,6 +15,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -43,7 +43,14 @@ import daily.dayo.presentation.theme.caption3 import java.text.DecimalFormat @Composable -fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean = false) { +fun HomePostView( + post: Post, + modifier: Modifier = Modifier, + isDayoPick: Boolean = false, + onClickPost: () -> Unit, + onClickLikePost: () -> Unit, + onClickNickname: () -> Unit +) { val imageInteractionSource = remember { MutableInteractionSource() } Column(modifier = modifier) { Box( @@ -51,7 +58,7 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .fillMaxWidth() .aspectRatio(1f) ) { - // Thumbnail Image + // thumbnail image AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data("${BuildConfig.BASE_URL}/images/${post.thumbnailImage}") @@ -64,11 +71,11 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .clickableSingle( interactionSource = imageInteractionSource, indication = null, - onClick = { } + onClick = { onClickPost() } ) ) - // Dayo Pick Icon + // dayo pick icon if (isDayoPick) { Image( imageVector = ImageVector.vectorResource(id = R.drawable.ic_dayo_pick), @@ -79,23 +86,33 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean ) } - // Like Button + // like button Image( imageVector = ImageVector.vectorResource(id = if (post.heart) R.drawable.ic_heart_filled else R.drawable.ic_heart), modifier = Modifier .padding(bottom = 12.dp, end = 11.dp) .align(Alignment.BottomEnd) - .clickable { }, + .clickableSingle( + indication = rememberRipple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + onClick = { onClickLikePost() } + ), contentDescription = "like Button", ) } Spacer(modifier = Modifier.height(8.dp)) - // Publisher Info + // publisher info Row( horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.wrapContentHeight() + modifier = Modifier + .wrapContentHeight() + .clickableSingle( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { onClickNickname() } + ) ) { AsyncImage( model = ImageRequest.Builder(LocalContext.current) @@ -107,18 +124,13 @@ fun HomePostView(post: Post, modifier: Modifier = Modifier, isDayoPick: Boolean .size(16.dp) .clip(shape = CircleShape) .align(Alignment.CenterVertically) - .clickableSingle( - interactionSource = imageInteractionSource, - indication = null, - onClick = { } - ) ) Text(text = post.nickname, style = MaterialTheme.typography.b5.copy(Gray1_313131)) } Spacer(modifier = Modifier.height(2.dp)) - // Post Info + // post info val dec = DecimalFormat("#,###") Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Text(text = stringResource(id = R.string.like) + " ${dec.format(post.heartCount)}", style = MaterialTheme.typography.caption3.copy(Gray3_9FA5AE)) @@ -152,7 +164,10 @@ private fun PreviewHomePostView() { hashtags = null, bookmark = null ), - isDayoPick = true + isDayoPick = true, + onClickPost = {}, + onClickLikePost = {}, + onClickNickname = {} ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt b/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt index 1b253ea8..69dc2378 100644 --- a/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt +++ b/presentation/src/main/java/daily/dayo/presentation/viewmodel/HomeViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import daily.dayo.presentation.common.Resource +import dagger.hilt.android.lifecycle.HiltViewModel import daily.dayo.domain.model.Category import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Post @@ -14,8 +14,11 @@ import daily.dayo.domain.usecase.post.RequestDayoPickPostListCategoryUseCase import daily.dayo.domain.usecase.post.RequestDayoPickPostListUseCase import daily.dayo.domain.usecase.post.RequestNewPostListCategoryUseCase import daily.dayo.domain.usecase.post.RequestNewPostListUseCase -import dagger.hilt.android.lifecycle.HiltViewModel +import daily.dayo.presentation.common.Resource import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -28,8 +31,13 @@ class HomeViewModel @Inject constructor( private val requestLikePostUseCase: RequestLikePostUseCase, private val requestUnlikePostUseCase: RequestUnlikePostUseCase ) : ViewModel() { - var currentDayoPickCategory = Category.ALL - var currentNewCategory = Category.ALL + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing: StateFlow + get() = _isRefreshing.asStateFlow() + + private val _currentCategory = MutableStateFlow(Category.ALL) + val currentCategory: StateFlow + get() = _currentCategory.asStateFlow() private val _dayoPickPostList = MutableLiveData>>() val dayoPickPostList: LiveData>> get() = _dayoPickPostList @@ -37,21 +45,41 @@ class HomeViewModel @Inject constructor( private val _newPostList = MutableLiveData>>() val newPostList: LiveData>> get() = _newPostList + fun setCategory(category: Category) { + viewModelScope.launch { + _currentCategory.emit(category) + } + } + + fun loadDayoPickPosts() { + viewModelScope.launch { + _isRefreshing.emit(true) + requestDayoPickPostList() + _isRefreshing.emit(false) + } + } + + fun loadNewPosts() { + viewModelScope.launch { + _isRefreshing.emit(true) + requestNewPostList() + _isRefreshing.emit(false) + } + } + fun requestDayoPickPostList() = viewModelScope.launch { _dayoPickPostList.postValue(Resource.loading(null)) - if (currentDayoPickCategory == Category.ALL) { - requestHomeDayoPickPostList() - } else { - requestHomeDayoPickPostListCategory(currentDayoPickCategory) + when (currentCategory.value) { + Category.ALL -> requestHomeDayoPickPostList() + else -> requestHomeDayoPickPostListCategory(currentCategory.value) } } fun requestNewPostList() = viewModelScope.launch { _newPostList.postValue(Resource.loading(null)) - if (currentNewCategory == Category.ALL) { - requestHomeNewPostList() - } else { - requestHomeNewPostListCategory(currentNewCategory) + when (currentCategory.value) { + Category.ALL -> requestHomeNewPostList() + else -> requestHomeNewPostListCategory(currentCategory.value) } } @@ -61,12 +89,15 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _newPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _newPostList.postValue(Resource.error(ApiResponse.exception.toString(), null)) } + is NetworkResponse.ApiError -> { _newPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _newPostList.postValue(Resource.error(ApiResponse.throwable.toString(), null)) } @@ -80,12 +111,15 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _newPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _newPostList.postValue(Resource.error(ApiResponse.exception.toString(), null)) } + is NetworkResponse.ApiError -> { _newPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _newPostList.postValue(Resource.error(ApiResponse.throwable.toString(), null)) } @@ -99,6 +133,7 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _dayoPickPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _dayoPickPostList.postValue( Resource.error( @@ -107,9 +142,11 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.ApiError -> { _dayoPickPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _dayoPickPostList.postValue( Resource.error( @@ -128,6 +165,7 @@ class HomeViewModel @Inject constructor( is NetworkResponse.Success -> { _dayoPickPostList.postValue(Resource.success(ApiResponse.body?.data)) } + is NetworkResponse.NetworkError -> { _dayoPickPostList.postValue( Resource.error( @@ -136,9 +174,11 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.ApiError -> { _dayoPickPostList.postValue(Resource.error(ApiResponse.error.toString(), null)) } + is NetworkResponse.UnknownError -> { _dayoPickPostList.postValue( Resource.error( @@ -172,6 +212,7 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.NetworkError -> {} is NetworkResponse.ApiError -> {} is NetworkResponse.UnknownError -> {} @@ -200,6 +241,7 @@ class HomeViewModel @Inject constructor( ) ) } + is NetworkResponse.NetworkError -> {} is NetworkResponse.ApiError -> {} is NetworkResponse.UnknownError -> {} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 12da9efb..aa15561d 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -68,6 +68,7 @@ 인기있는 다꾸가 아직 없어요 다꾸들을 구경하고 반응을 남겨보세요 구경하러 가기 + 실시간으로 올라온 게시글이에요! 새로운 다꾸가 아직 없어요 여러분의 다꾸를 공유해보세요 공유하러 가기 From 5742bb180d9b3199174c844544f069e4a6bf8f33 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Fri, 1 Mar 2024 01:51:51 +0900 Subject: [PATCH 20/24] #546 [layout] Home Empty View --- .../screen/home/HomeDayoPickScreen.kt | 170 +++++++++++------- .../presentation/screen/home/HomeNewScreen.kt | 167 ++++++++++------- 2 files changed, 213 insertions(+), 124 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt index 0e601543..97642c4f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeDayoPickScreen.kt @@ -1,13 +1,16 @@ package daily.dayo.presentation.screen.home +import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row 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.width import androidx.compose.foundation.layout.wrapContentHeight @@ -29,20 +32,30 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status import daily.dayo.presentation.theme.Gray2_767B83 +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.Gray4_C5CAD2 import daily.dayo.presentation.theme.Gray6_F0F1F3 import daily.dayo.presentation.theme.White_FFFFFF +import daily.dayo.presentation.theme.b3 +import daily.dayo.presentation.theme.caption1 import daily.dayo.presentation.theme.caption3 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.FilledButton import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope @@ -59,86 +72,117 @@ fun HomeDayoPickScreen( val dayoPickPostList = homeViewModel.dayoPickPostList.observeAsState() val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadDayoPickPosts() }) - Box(Modifier.pullRefresh(pullRefreshState)) { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDCA1", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) - - Text( - text = stringResource(id = R.string.home_dayopick_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + var isEmpty by rememberSaveable { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier.wrapContentHeight() + ) { + // description + item(span = { GridItemSpan(maxLineSpan) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDCA1", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_dayopick_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // dayo pick list - when (dayoPickPostList.value?.status) { - Status.SUCCESS -> { - dayoPickPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView( - post = post, - isDayoPick = index in 0..4, - modifier = Modifier.padding(bottom = 20.dp), - onClickPost = { - // todo move to post - }, - onClickLikePost = { - if (!post.heart) { - homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) - } else { - homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + // dayo pick list + when (dayoPickPostList.value?.status) { + Status.SUCCESS -> { + isEmpty = dayoPickPostList.value?.data?.isEmpty() == true + dayoPickPostList.value?.data?.mapIndexed { index, post -> + item { + HomePostView( + post = post, + isDayoPick = index in 0..4, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = true) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = true) + } + }, + onClickNickname = { + // todo move to profile } - }, - onClickNickname = { - // todo move to profile - } - ) + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } + + // empty view + if (isEmpty) HomeDayoPickEmptyView() } + // refresh indicator PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } } +@Composable +private fun HomeDayoPickEmptyView() { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image(imageVector = ImageVector.vectorResource(id = R.drawable.ic_home_empty), contentDescription = null) + Spacer(modifier = Modifier.height(20.dp)) + + Text(text = stringResource(id = R.string.home_dayopick_empty_title), style = MaterialTheme.typography.b3.copy(Gray3_9FA5AE)) + Spacer(modifier = Modifier.height(2.dp)) + Text(text = stringResource(id = R.string.home_dayopick_empty_detail), style = MaterialTheme.typography.caption1.copy(Gray4_C5CAD2)) + + Spacer(modifier = Modifier.height(28.dp)) + FilledButton(onClick = { /*TODO*/ }, label = stringResource(id = R.string.home_dayopick_empty_action)) + } +} + @OptIn(ExperimentalMaterialApi::class) @Composable fun CategoryButton( diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt index f6d9219f..af12fd4f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeNewScreen.kt @@ -1,11 +1,15 @@ package daily.dayo.presentation.screen.home +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +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.lazy.grid.GridCells @@ -21,16 +25,26 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import daily.dayo.presentation.R import daily.dayo.presentation.common.Status +import daily.dayo.presentation.theme.Gray3_9FA5AE +import daily.dayo.presentation.theme.Gray4_C5CAD2 +import daily.dayo.presentation.theme.b3 +import daily.dayo.presentation.theme.caption1 import daily.dayo.presentation.view.EmojiView +import daily.dayo.presentation.view.FilledButton import daily.dayo.presentation.view.HomePostView import daily.dayo.presentation.viewmodel.HomeViewModel import kotlinx.coroutines.CoroutineScope @@ -46,81 +60,112 @@ fun HomeNewScreen( val newPostList = homeViewModel.newPostList.observeAsState() val refreshing by homeViewModel.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(refreshing, { homeViewModel.loadNewPosts() }) - Box(Modifier.pullRefresh(pullRefreshState)) { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues(horizontal = 18.dp), - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - ) { - // description - item(span = { GridItemSpan(2) }) { - Row( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(top = 8.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row { - EmojiView( - emoji = "\uD83D\uDC40", - emojiSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = Modifier.align(Alignment.CenterVertically) - ) + var isEmpty by rememberSaveable { mutableStateOf(false) } - Text( - text = stringResource(id = R.string.home_new_description), - style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), - modifier = Modifier - .padding(horizontal = 4.dp) - .align(Alignment.CenterVertically) - ) + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(horizontal = 18.dp), + modifier = Modifier.wrapContentHeight() + ) { + // description + item(span = { GridItemSpan(2) }) { + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(top = 8.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row { + EmojiView( + emoji = "\uD83D\uDC40", + emojiSize = MaterialTheme.typography.bodyMedium.fontSize, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Text( + text = stringResource(id = R.string.home_new_description), + style = MaterialTheme.typography.bodyMedium.copy(Color(0xFF73777C)), + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterVertically) + ) + } + CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - CategoryButton(selectedCategoryName, coroutineScope, bottomSheetState) } - } - // new post list - when (newPostList.value?.status) { - Status.SUCCESS -> { - newPostList.value?.data?.mapIndexed { index, post -> - item { - HomePostView( - post = post, - modifier = Modifier.padding(bottom = 20.dp), - onClickPost = { - // todo move to post - }, - onClickLikePost = { - if (!post.heart) { - homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) - } else { - homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + // new post list + when (newPostList.value?.status) { + Status.SUCCESS -> { + isEmpty = newPostList.value?.data?.isEmpty() == true + newPostList.value?.data?.mapIndexed { _, post -> + item { + HomePostView( + post = post, + modifier = Modifier.padding(bottom = 20.dp), + onClickPost = { + // todo move to post + }, + onClickLikePost = { + if (!post.heart) { + homeViewModel.requestLikePost(post.postId!!, isDayoPickLike = false) + } else { + homeViewModel.requestUnlikePost(post.postId!!, isDayoPickLike = false) + } + }, + onClickNickname = { + // todo move to profile } - }, - onClickNickname = { - // todo move to profile - } - ) + ) + } } } - } - Status.LOADING -> { - } + Status.LOADING -> { + } - Status.ERROR -> { + Status.ERROR -> { - } + } - else -> {} + else -> {} + } } + + // empty view + if (isEmpty) HomeNewEmptyView() } + // refresh indicator PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) } +} + +@Composable +private fun HomeNewEmptyView() { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image(imageVector = ImageVector.vectorResource(id = R.drawable.ic_home_empty), contentDescription = null) + Spacer(modifier = Modifier.height(20.dp)) + + Text(text = stringResource(id = R.string.home_new_empty_title), style = MaterialTheme.typography.b3.copy(Gray3_9FA5AE)) + Spacer(modifier = Modifier.height(2.dp)) + Text(text = stringResource(id = R.string.home_new_empty_detail), style = MaterialTheme.typography.caption1.copy(Gray4_C5CAD2)) + + Spacer(modifier = Modifier.height(28.dp)) + FilledButton(onClick = { /*TODO*/ }, label = stringResource(id = R.string.home_new_empty_action)) + } } \ No newline at end of file From 79e87e1edb9ac5abc7372315bd0d157ab761fd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EB=8F=99=EC=A4=80?= Date: Sat, 24 Feb 2024 23:27:34 +0900 Subject: [PATCH 21/24] [feature] Add a parameter to modify the default and check colors in BottomSheetDialog --- .../java/daily/dayo/presentation/view/BottomSheetDialog.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt index abdcbd4e..fd57591d 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt @@ -37,6 +37,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign @@ -64,6 +65,8 @@ fun BottomSheetDialog( buttons: List Unit>>, leftIconButtons: List? = null, isFirstButtonColored: Boolean = false, + normalColor: Color = Gray1_313131, + checkedColor: Color = PrimaryGreen_23C882, title: String = "", titleButtonAction: () -> Unit = {}, rightIcon: ImageVector = ImageVector.vectorResource(id = R.drawable.ic_check_mark), @@ -146,7 +149,7 @@ fun BottomSheetDialog( imageVector = leftIconButtons[index], contentDescription = "", modifier = Modifier.align(Alignment.CenterVertically), - tint = if (checkedButtonIndex == index) PrimaryGreen_23C882 else LocalContentColor.current + tint = if (checkedButtonIndex == index) checkedColor else normalColor ) } Text( @@ -155,7 +158,7 @@ fun BottomSheetDialog( if (leftIconButtons == null) 0.dp else 8.dp, 0.dp ), - color = if ((isFirstButtonColored && index == 0) || (checkedButtonIndex == index)) PrimaryGreen_23C882 else Gray1_313131, + color = if ((isFirstButtonColored && index == 0) || (checkedButtonIndex == index)) checkedColor else normalColor, fontSize = 16.sp, style = MaterialTheme.typography.b4 ) From 0a79e38d424235fb2c56ebc6c0b7a1fb2b87be18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=97=88=EB=8F=99=EC=A4=80?= Date: Sun, 24 Mar 2024 16:51:51 +0900 Subject: [PATCH 22/24] [feature] Add left checked icon buttons parameter --- .../daily/dayo/presentation/view/BottomSheetDialog.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt index fd57591d..53d029c0 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt @@ -20,13 +20,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.LocalContentColor import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Image +import androidx.compose.material.icons.filled.ImageSearch import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -64,6 +64,7 @@ fun BottomSheetDialog( sheetState: ModalBottomSheetState, buttons: List Unit>>, leftIconButtons: List? = null, + leftIconCheckedButtons: List? = null, isFirstButtonColored: Boolean = false, normalColor: Color = Gray1_313131, checkedColor: Color = PrimaryGreen_23C882, @@ -144,12 +145,11 @@ fun BottomSheetDialog( ), horizontalArrangement = if (leftIconButtons == null) Arrangement.Center else Arrangement.SpaceBetween, ) { - if (leftIconButtons != null) { + if (leftIconButtons != null && leftIconCheckedButtons != null) { Icon( - imageVector = leftIconButtons[index], + imageVector = if (checkedButtonIndex == index) leftIconCheckedButtons[index] else leftIconButtons[index], contentDescription = "", - modifier = Modifier.align(Alignment.CenterVertically), - tint = if (checkedButtonIndex == index) checkedColor else normalColor + modifier = Modifier.align(Alignment.CenterVertically) ) } Text( @@ -295,6 +295,7 @@ fun PreviewMyBottomSheetDialog() { buttons = listOf(Pair("contents") { }, Pair("contents") { }, Pair("contents") { }), title = "title", leftIconButtons = listOf(Icons.Default.Image, Icons.Default.Image, Icons.Default.Image), + leftIconCheckedButtons = listOf(Icons.Default.ImageSearch, Icons.Default.ImageSearch, Icons.Default.ImageSearch), checkedButtonIndex = 0, ) } From e83b18f2f8d7c1058b93044f1c253980b273c8e2 Mon Sep 17 00:00:00 2001 From: DongJun Huh Date: Wed, 27 Mar 2024 23:13:17 +0900 Subject: [PATCH 23/24] [feature] Change Icon Default Color from black to unspecified --- .../java/daily/dayo/presentation/view/BottomSheetDialog.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt index 53d029c0..ebc4a575 100644 --- a/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt +++ b/presentation/src/main/java/daily/dayo/presentation/view/BottomSheetDialog.kt @@ -149,7 +149,8 @@ fun BottomSheetDialog( Icon( imageVector = if (checkedButtonIndex == index) leftIconCheckedButtons[index] else leftIconButtons[index], contentDescription = "", - modifier = Modifier.align(Alignment.CenterVertically) + modifier = Modifier.align(Alignment.CenterVertically), + tint = Color.Unspecified ) } Text( @@ -168,7 +169,8 @@ fun BottomSheetDialog( Icon( imageVector = rightIcon, contentDescription = "", - modifier = Modifier.align(Alignment.CenterVertically) + modifier = Modifier.align(Alignment.CenterVertically), + tint = Color.Unspecified ) } } From 166229361706304fabdef2b2bb2fa6da694b8bb1 Mon Sep 17 00:00:00 2001 From: Ju YunGyeom Date: Wed, 27 Mar 2024 23:39:14 +0900 Subject: [PATCH 24/24] #546 [layout] Checked Category Icon --- .../presentation/screen/home/HomeScreen.kt | 23 +++++++----- .../res/drawable/ic_category_all_checked.xml | 33 +++++++++++++++++ .../drawable/ic_category_digital_checked.xml | 21 +++++++++++ .../res/drawable/ic_category_etc_checked.xml | 15 ++++++++ .../ic_category_pocketbook_checked.xml | 12 +++++++ .../ic_category_scheduler_checked.xml | 32 +++++++++++++++++ .../ic_category_sixholediary_checked.xml | 36 +++++++++++++++++++ .../ic_category_studyplanner_checked.xml | 15 ++++++++ 8 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 presentation/src/main/res/drawable/ic_category_all_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_digital_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_etc_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_pocketbook_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_scheduler_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_sixholediary_checked.xml create mode 100644 presentation/src/main/res/drawable/ic_category_studyplanner_checked.xml diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt index 055e19fb..9a4d7801 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/home/HomeScreen.kt @@ -31,7 +31,9 @@ import androidx.navigation.compose.rememberNavController import daily.dayo.domain.model.Category import daily.dayo.presentation.R import daily.dayo.presentation.theme.Gray1_313131 +import daily.dayo.presentation.theme.Gray2_767B83 import daily.dayo.presentation.theme.Gray5_E8EAEE +import daily.dayo.presentation.theme.PrimaryGreen_23C882 import daily.dayo.presentation.view.BottomSheetDialog import daily.dayo.presentation.view.TextButton import daily.dayo.presentation.view.TopNavigation @@ -155,6 +157,11 @@ private fun CategoryBottomSheetDialog( leftIconButtons = categoryMenus.map { ImageVector.vectorResource(it.defaultIcon) }, + leftIconCheckedButtons = categoryMenus.map { + ImageVector.vectorResource(it.checkedIcon) + }, + normalColor = Gray2_767B83, + checkedColor = PrimaryGreen_23C882, checkedButtonIndex = selectedCategory.second, closeButtonAction = { coroutineScope.launch { bottomSheetState.hide() } } ) @@ -170,12 +177,12 @@ private fun PreviewHomeScreen() { } -sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int, val category: Category) { - object All : CategoryMenu("전체", R.drawable.ic_category_all, Category.ALL) - object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler, Category.SCHEDULER) - object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner, Category.STUDY_PLANNER) - object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook, Category.POCKET_BOOK) - object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary, Category.SIX_DIARY) - object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital, Category.GOOD_NOTE) - object ETC : CategoryMenu("기타", R.drawable.ic_category_etc, Category.STUDY_PLANNER) +sealed class CategoryMenu(val name: String, @DrawableRes val defaultIcon: Int, @DrawableRes val checkedIcon: Int, val category: Category) { + object All : CategoryMenu("전체", R.drawable.ic_category_all, R.drawable.ic_category_all_checked, Category.ALL) + object Scheduler : CategoryMenu("스케줄러", R.drawable.ic_category_scheduler, R.drawable.ic_category_scheduler_checked, Category.SCHEDULER) + object StudyPlanner : CategoryMenu("스터디 플래너", R.drawable.ic_category_studyplanner, R.drawable.ic_category_studyplanner_checked, Category.STUDY_PLANNER) + object PocketBook : CategoryMenu("포켓북", R.drawable.ic_category_pocketbook, R.drawable.ic_category_pocketbook_checked, Category.POCKET_BOOK) + object SixHoleDiary : CategoryMenu("6공 다이어리", R.drawable.ic_category_sixholediary, R.drawable.ic_category_sixholediary_checked, Category.SIX_DIARY) + object Digital : CategoryMenu("모바일 다이어리", R.drawable.ic_category_digital, R.drawable.ic_category_digital_checked, Category.GOOD_NOTE) + object ETC : CategoryMenu("기타", R.drawable.ic_category_etc, R.drawable.ic_category_etc_checked, Category.STUDY_PLANNER) } diff --git a/presentation/src/main/res/drawable/ic_category_all_checked.xml b/presentation/src/main/res/drawable/ic_category_all_checked.xml new file mode 100644 index 00000000..130900dc --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_all_checked.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_digital_checked.xml b/presentation/src/main/res/drawable/ic_category_digital_checked.xml new file mode 100644 index 00000000..6174b019 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_digital_checked.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_etc_checked.xml b/presentation/src/main/res/drawable/ic_category_etc_checked.xml new file mode 100644 index 00000000..ca178dec --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_etc_checked.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_pocketbook_checked.xml b/presentation/src/main/res/drawable/ic_category_pocketbook_checked.xml new file mode 100644 index 00000000..0292d8ce --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_pocketbook_checked.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_scheduler_checked.xml b/presentation/src/main/res/drawable/ic_category_scheduler_checked.xml new file mode 100644 index 00000000..3c39e1f8 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_scheduler_checked.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_sixholediary_checked.xml b/presentation/src/main/res/drawable/ic_category_sixholediary_checked.xml new file mode 100644 index 00000000..d5426667 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_sixholediary_checked.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_category_studyplanner_checked.xml b/presentation/src/main/res/drawable/ic_category_studyplanner_checked.xml new file mode 100644 index 00000000..5ff5a2b8 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_category_studyplanner_checked.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file