diff --git a/core/navigation/src/main/java/in/koreatech/koin/core/navigation/Navigator.kt b/core/navigation/src/main/java/in/koreatech/koin/core/navigation/Navigator.kt index fa93473539..87220d59d0 100644 --- a/core/navigation/src/main/java/in/koreatech/koin/core/navigation/Navigator.kt +++ b/core/navigation/src/main/java/in/koreatech/koin/core/navigation/Navigator.kt @@ -29,4 +29,8 @@ interface Navigator { fun navigateToStore( context: Context ): Intent + + fun navigateToChatRoom( + context: Context + ): Intent } diff --git a/domain/src/main/java/in/koreatech/koin/domain/util/DateFormatUtil.kt b/domain/src/main/java/in/koreatech/koin/domain/util/DateFormatUtil.kt index ccbf4fb7e2..962fe959b7 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/util/DateFormatUtil.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/util/DateFormatUtil.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.domain.util -import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter import java.util.Calendar import java.util.Date import java.util.Locale @@ -19,11 +20,12 @@ object DateFormatUtil { /** * yyyy-MM-dd HH:mm:ss -> MM.dd */ - fun getSimpleMonthAndDay(dateString: String): String { - val originalFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - val targetFormat = SimpleDateFormat("MM.dd", Locale.getDefault()) - val date = originalFormat.parse(dateString) - return targetFormat.format(date) + fun getSimpleMonthAndDay(date: LocalDate): String { + return DateTimeFormatter.ofPattern("MM.dd").format(date) + } + + fun getFullDate(date: LocalDate): String { + return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(date) } fun dayOfWeekToIndex(dayOfWeek: String): Int { diff --git a/feature/lostandfound/.gitignore b/feature/article/.gitignore similarity index 100% rename from feature/lostandfound/.gitignore rename to feature/article/.gitignore diff --git a/feature/lostandfound/build.gradle.kts b/feature/article/build.gradle.kts similarity index 64% rename from feature/lostandfound/build.gradle.kts rename to feature/article/build.gradle.kts index 2f6ee7c838..10890292ba 100644 --- a/feature/lostandfound/build.gradle.kts +++ b/feature/article/build.gradle.kts @@ -5,7 +5,12 @@ plugins { } android { - namespace = "in.koreatech.koin.feature.lostandfound" + namespace = "in.koreatech.koin.feature.article" + + buildFeatures { + dataBinding = true + viewBinding = true + } } dependencies { @@ -14,13 +19,17 @@ dependencies { implementation(projects.core.onboarding) implementation(projects.core.designsystem) implementation(projects.core.analytics) + implementation(projects.core.navigation) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) + implementation(libs.androidx.navigation.fragment.ktx) + implementation(libs.androidx.navigation.ui.ktx) implementation(libs.material) implementation(libs.coil.compose) implementation(libs.coil.gif) + implementation(libs.glide) implementation(libs.timber) } diff --git a/feature/lostandfound/consumer-rules.pro b/feature/article/consumer-rules.pro similarity index 100% rename from feature/lostandfound/consumer-rules.pro rename to feature/article/consumer-rules.pro diff --git a/feature/lostandfound/proguard-rules.pro b/feature/article/proguard-rules.pro similarity index 100% rename from feature/lostandfound/proguard-rules.pro rename to feature/article/proguard-rules.pro diff --git a/feature/article/src/main/AndroidManifest.xml b/feature/article/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..b2fafaca0e --- /dev/null +++ b/feature/article/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ArticleActivity.kt similarity index 91% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ArticleActivity.kt index 8bd2e31ed3..f4e5efa9d8 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ArticleActivity.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article import android.content.Intent import android.os.Bundle @@ -10,7 +10,6 @@ import androidx.core.view.updatePadding import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.ActivityBase import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction @@ -20,9 +19,11 @@ import `in`.koreatech.koin.core.navigation.utils.EXTRA_BOARD_ID import `in`.koreatech.koin.core.navigation.utils.EXTRA_ID import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.core.util.whiteStatusBar -import `in`.koreatech.koin.databinding.ActivityArticleBinding -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.ARTICLE_ID -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID +import `in`.koreatech.koin.feature.article.databinding.ActivityArticleBinding +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.model.ArticleToolbarState +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.ARTICLE_ID +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID import timber.log.Timber @AndroidEntryPoint @@ -48,10 +49,9 @@ class ArticleActivity : ActivityBase() { window.whiteStatusBar() - val navHostFragment = - supportFragmentManager.findFragmentById( - R.id.nav_host_article_fragment - ) as NavHostFragment + val navHostFragment = supportFragmentManager.findFragmentById( + R.id.nav_host_article_fragment + ) as NavHostFragment navController = navHostFragment.navController navController.addOnDestinationChangedListener { _, dest, _ -> @@ -63,9 +63,11 @@ class ArticleActivity : ActivityBase() { R.id.articleLostAndFoundWriteLostFragment -> setToolbar( ArticleToolbarState.ARTICLE_LOSTANDFOUND_LOST_ITEM ) + R.id.articleLostAndFoundWriteFoundFragment -> setToolbar( ArticleToolbarState.ARTICLE_LOSTANDFOUND_FOUND_ITEM ) + R.id.articleLostAndFoundDetailFragment -> setToolbar( ArticleToolbarState.ARTICLE_DETAIL ) @@ -99,6 +101,7 @@ class ArticleActivity : ActivityBase() { R.id.articleKeywordFragment ) // See ArticleKeywordFragment, LoginActivity } + "article_detail" -> { setNavigationGraph() val articleId = uri.getQueryParameter("article_id")?.toIntOrNull() ?: 0 @@ -112,9 +115,11 @@ class ArticleActivity : ActivityBase() { ) ) } + "article_lost_and_found" -> { setNavigationGraph(ArticleBoardType.LOSTANDFOUND.id) } + null -> { val bundle = intent.getBundleExtra(BUNDLE_ARTICLE_EXTRA_KEY) bundle?.getInt(START_BOARD)?.let { diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/Constant.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/Constant.kt similarity index 75% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/Constant.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/Constant.kt index d7007c7093..3e1421d71d 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/Constant.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/Constant.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound +package `in`.koreatech.koin.feature.article const val IMAGE_MAX_COUNT = 10 const val DESCRIPTION_MAX_LENGTH = 1000 diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/LostAndFoundReportActivity.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/LostAndFoundReportActivity.kt similarity index 68% rename from koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/LostAndFoundReportActivity.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/LostAndFoundReportActivity.kt index 3345bd3eed..0502e7fc99 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/LostAndFoundReportActivity.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/LostAndFoundReportActivity.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.lostandfound +package `in`.koreatech.koin.feature.article import android.content.Intent import android.os.Bundle @@ -7,10 +7,8 @@ import androidx.activity.compose.setContent import androidx.core.os.bundleOf import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.core.designsystem.util.enableEdgeToEdgeWithDarkStatusBar -import `in`.koreatech.koin.feature.lostandfound.ui.report.LostAndFoundReport -import `in`.koreatech.koin.ui.article.ArticleActivity -import `in`.koreatech.koin.ui.article.ArticleActivity.Companion.BUNDLE_ARTICLE_EXTRA_KEY -import `in`.koreatech.koin.ui.article.ArticleBoardType +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.ui.lostandfound.report.LostAndFoundReport @AndroidEntryPoint class LostAndFoundReportActivity : ComponentActivity() { @@ -24,9 +22,9 @@ class LostAndFoundReportActivity : ComponentActivity() { startActivity( Intent(this, ArticleActivity::class.java).apply { putExtra( - BUNDLE_ARTICLE_EXTRA_KEY, + ArticleActivity.Companion.BUNDLE_ARTICLE_EXTRA_KEY, bundleOf( - ArticleActivity.START_BOARD to ArticleBoardType.LOSTANDFOUND.id + ArticleActivity.Companion.START_BOARD to ArticleBoardType.LOSTANDFOUND.id ) ) } diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/Dropdown.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/Dropdown.kt similarity index 93% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/Dropdown.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/Dropdown.kt index 43dd032c0e..a05199d985 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/Dropdown.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/Dropdown.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background @@ -26,7 +26,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R /** * Dropdown Component @@ -49,8 +49,7 @@ fun Dropdown( ) Row( - modifier = - Modifier + modifier = Modifier .clip(KoinTheme.shapes.medium) .background( color = KoinTheme.colors.info200 @@ -81,8 +80,7 @@ fun Dropdown( * @see [androidx.compose.material3.DropdownMenu] */ DropdownMenu( - modifier = - Modifier + modifier = Modifier .width(96.dp) .padding(0.dp), expanded = isDropdownExpanded, @@ -92,8 +90,7 @@ fun Dropdown( shadowElevation = 0.dp ) { Box( - modifier = - Modifier + modifier = Modifier .fillMaxSize() .clip(KoinTheme.shapes.medium) .background( @@ -106,8 +103,7 @@ fun Dropdown( text = it, style = KoinTheme.typography.medium14.copy(textAlign = TextAlign.Center), color = KoinTheme.colors.primary600, - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .noRippleClickable { onItemSelected(index) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/HotArticle.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/HotArticle.kt similarity index 84% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/HotArticle.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/HotArticle.kt index 5be5167253..cb0b93d01c 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/HotArticle.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/HotArticle.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -18,9 +18,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.ArticleBoardType -import `in`.koreatech.koin.feature.lostandfound.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState @Composable fun HotArticle( @@ -37,8 +37,7 @@ fun HotArticle( hotArticleList.forEach { hotArticle -> HotArticleItem( - hotArticleData = - HotArticleData( + hotArticleData = HotArticleData( articleId = hotArticle.id, articleTitle = hotArticle.title, board = hotArticle.board @@ -57,8 +56,7 @@ fun HotArticleItem( navigateToHotArticle: (HotArticleData) -> Unit ) { Row( - modifier = - modifier + modifier = modifier .fillMaxWidth() .noRippleClickable { navigateToHotArticle(hotArticleData) } .padding(vertical = 12.dp, horizontal = 24.dp), @@ -66,8 +64,7 @@ fun HotArticleItem( ) { Text( text = stringResource(hotArticleData.board.koreanName), - style = - KoinTheme.typography.bold12.copy( + style = KoinTheme.typography.bold12.copy( fontWeight = FontWeight.SemiBold, color = KoinTheme.colors.primary600 ) @@ -77,8 +74,7 @@ fun HotArticleItem( text = hotArticleData.articleTitle, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = - KoinTheme.typography.bold14.copy( + style = KoinTheme.typography.bold14.copy( fontWeight = FontWeight.SemiBold, color = Color.Black ) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/ItemTypeChip.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/ItemTypeChip.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/ItemTypeChip.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/ItemTypeChip.kt index 95b2a95f14..a6d8ede8db 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/ItemTypeChip.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/ItemTypeChip.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -8,7 +8,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostItemCategory @Composable fun ItemTypeChip( diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/KeywordChipGroup.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/KeywordChipGroup.kt similarity index 98% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/KeywordChipGroup.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/KeywordChipGroup.kt index be512947e1..f4d70cde8f 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/KeywordChipGroup.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/KeywordChipGroup.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -118,8 +118,7 @@ internal fun LostAndFoundTextChipScrollGroup( .drawWithContent { drawContent() drawRect( - brush = - Brush.horizontalGradient( + brush = Brush.horizontalGradient( 0f to Color.White, 0.1f to Color.Transparent, 0.9f to Color.Transparent, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LoadingDialog.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/LoadingDialog.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LoadingDialog.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/LoadingDialog.kt index 556b4683de..827b2990f6 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LoadingDialog.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/LoadingDialog.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Row @@ -16,7 +16,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun LoadingDialog() { @@ -25,8 +25,7 @@ fun LoadingDialog() { properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) ) { Row( - modifier = - Modifier + modifier = Modifier .background(KoinTheme.colors.primary500) .padding(vertical = 24.dp, horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LostItemTypeChip.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/LostItemTypeChip.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LostItemTypeChip.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/component/LostItemTypeChip.kt index dece3e53a4..b33bc87df9 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/component/LostItemTypeChip.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/component/LostItemTypeChip.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.component +package `in`.koreatech.koin.feature.article.component import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.shape.RoundedCornerShape @@ -11,7 +11,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.chip.TextChipDefaults import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostItemCategory /** * 분실물 종류 칩 @@ -48,8 +48,7 @@ fun ReadOnlyTextChip( showClickRipple = false, onSelect = {}, contentPadding = contentPadding, - chipColors = - TextChipDefaults.chipColors( + chipColors = TextChipDefaults.chipColors( selectedContainerColor = chipColor, unselectedContainerColor = chipColor, selectedContentColor = textColor, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ArticleBoardType.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ArticleBoardType.kt similarity index 74% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ArticleBoardType.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ArticleBoardType.kt index 0db5a7969d..a13cd13231 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ArticleBoardType.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ArticleBoardType.kt @@ -1,11 +1,11 @@ -package `in`.koreatech.koin.feature.lostandfound.enums +package `in`.koreatech.koin.feature.article.enums /* Included from main koin module because we can't access ArticleBoardType from lostandfound module */ import androidx.annotation.StringRes -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R enum class ArticleBoardType( val id: Int, @@ -22,7 +22,24 @@ enum class ArticleBoardType( RECRUIT(8, R.string.article_recruit, R.string.article_recruit_simple, LinkType.STEMS), IPP(12, R.string.article_ipp, R.string.article_ipp_simple, LinkType.PORTAL), STUDENT(13, R.string.article_student, R.string.article_student_simple, LinkType.PORTAL, false), - KOIN(9, R.string.article_koin, R.string.article_koin, LinkType.NONE, false) + KOIN(9, R.string.article_koin, R.string.article_koin, LinkType.NONE, false); + + companion object { + fun fromId(id: Int): ArticleBoardType { + return when (id) { + 4 -> ALL + 14 -> LOSTANDFOUND + 5 -> NORMAL + 6 -> SCHOLARSHIP + 7 -> SCHOOL + 8 -> RECRUIT + 12 -> IPP + 13 -> STUDENT + 9 -> KOIN + else -> ALL + } + } + } } /** diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostItemCategory.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostItemCategory.kt similarity index 91% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostItemCategory.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostItemCategory.kt index 97999a9bea..5c640f6e71 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostItemCategory.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostItemCategory.kt @@ -1,7 +1,7 @@ -package `in`.koreatech.koin.feature.lostandfound.enums +package `in`.koreatech.koin.feature.article.enums import androidx.annotation.StringRes -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R enum class LostItemCategory( val id: Int, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostOrFoundType.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostOrFoundType.kt similarity index 64% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostOrFoundType.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostOrFoundType.kt index f723894911..7d46205c58 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/LostOrFoundType.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/LostOrFoundType.kt @@ -1,7 +1,7 @@ -package `in`.koreatech.koin.feature.lostandfound.enums +package `in`.koreatech.koin.feature.article.enums import androidx.annotation.StringRes -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R enum class LostOrFoundType( @StringRes val stringRes: Int diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ReportReason.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ReportReason.kt similarity index 88% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ReportReason.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ReportReason.kt index 5411fd9bfe..0599df02ac 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/enums/ReportReason.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/enums/ReportReason.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.enums +package `in`.koreatech.koin.feature.article.enums enum class ReportReason(val title: String, val description: String) { OFF_TOPIC("주제에 맞지 않음", "분실물과 관련 없는 글입니다."), diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/model/ArticleHeaderState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleHeaderState.kt similarity index 78% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/model/ArticleHeaderState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleHeaderState.kt index 944cdf2cec..acdefd5358 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/model/ArticleHeaderState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleHeaderState.kt @@ -1,15 +1,13 @@ -package `in`.koreatech.koin.feature.lostandfound.model +package `in`.koreatech.koin.feature.article.model import android.os.Parcelable import `in`.koreatech.koin.domain.model.article.ArticleHeader -import `in`.koreatech.koin.feature.lostandfound.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType import java.time.LocalDate import kotlinx.parcelize.Parcelize /** * For hot articles - * from main koin module - * @see `in`.koreatech.koin.feature.main.model.ArticleHeaderState */ @Parcelize data class ArticleHeaderState( diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleToolbarState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleToolbarState.kt similarity index 86% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleToolbarState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleToolbarState.kt index 0765edd706..446add0411 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleToolbarState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/model/ArticleToolbarState.kt @@ -1,8 +1,8 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.model import androidx.annotation.MenuRes import androidx.annotation.StringRes -import `in`.koreatech.koin.R +import `in`.koreatech.koin.feature.article.R enum class ArticleToolbarState( @StringRes val title: Int, diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/ArticleAdapter.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/ArticleAdapter.kt similarity index 59% rename from koin/src/main/java/in/koreatech/koin/ui/article/adapter/ArticleAdapter.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/ArticleAdapter.kt index 415e67604a..54de204a53 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/ArticleAdapter.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/ArticleAdapter.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.adapter +package `in`.koreatech.koin.feature.article.ui.article.adapter import android.text.TextUtils import android.view.LayoutInflater @@ -6,10 +6,11 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import `in`.koreatech.koin.databinding.ItemArticleHeaderBinding import `in`.koreatech.koin.domain.util.DateFormatUtil -import `in`.koreatech.koin.domain.util.TimeUtil -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState +import `in`.koreatech.koin.feature.article.databinding.ItemArticleHeaderBinding +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import java.time.ZoneId +import java.util.Date class ArticleAdapter( private val onClick: (ArticleHeaderState) -> Unit @@ -36,12 +37,11 @@ class ArticleAdapter( textViewArticleTitle.text = articleHeader.title.trim() textViewArticleAuthor.text = articleHeader.author try { - textViewArticleDate.text = - TextUtils.concat( - DateFormatUtil.getSimpleMonthAndDay(articleHeader.registeredAt), - " ", - DateFormatUtil.getDayOfWeek(TimeUtil.stringToDateYYYYMMDD(articleHeader.registeredAt)) - ) + textViewArticleDate.text = TextUtils.concat( + DateFormatUtil.getSimpleMonthAndDay(articleHeader.registeredAt), + " ", + DateFormatUtil.getDayOfWeek(Date.from(articleHeader.registeredAt.atStartOfDay(ZoneId.systemDefault()).toInstant())) + ) } catch (e: Exception) { } textViewArticleViewCount.text = articleHeader.viewCount.toString() @@ -52,21 +52,20 @@ class ArticleAdapter( } companion object { - private val diffUtil = - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState - ): Boolean { - return oldItem.id == newItem.id - } + private val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ArticleHeaderState, + newItem: ArticleHeaderState + ): Boolean { + return oldItem.id == newItem.id + } - override fun areContentsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState - ): Boolean { - return oldItem == newItem - } + override fun areContentsTheSame( + oldItem: ArticleHeaderState, + newItem: ArticleHeaderState + ): Boolean { + return oldItem == newItem } + } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/AttachmentAdapter.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/AttachmentAdapter.kt similarity index 67% rename from koin/src/main/java/in/koreatech/koin/ui/article/adapter/AttachmentAdapter.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/AttachmentAdapter.kt index dcecc9f871..b80ca2fba8 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/AttachmentAdapter.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/AttachmentAdapter.kt @@ -1,12 +1,12 @@ -package `in`.koreatech.koin.ui.article.adapter +package `in`.koreatech.koin.feature.article.ui.article.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import `in`.koreatech.koin.databinding.ItemAttachmentBinding -import `in`.koreatech.koin.ui.article.state.AttachmentState +import `in`.koreatech.koin.feature.article.databinding.ItemAttachmentBinding +import `in`.koreatech.koin.feature.article.ui.article.state.AttachmentState class AttachmentAdapter( private val onClick: (AttachmentState) -> Unit, @@ -45,21 +45,20 @@ class AttachmentAdapter( } companion object { - private val diffUtil = - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: AttachmentState, - newItem: AttachmentState - ): Boolean { - return oldItem.url == newItem.url - } + private val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AttachmentState, + newItem: AttachmentState + ): Boolean { + return oldItem.url == newItem.url + } - override fun areContentsTheSame( - oldItem: AttachmentState, - newItem: AttachmentState - ): Boolean { - return oldItem == newItem - } + override fun areContentsTheSame( + oldItem: AttachmentState, + newItem: AttachmentState + ): Boolean { + return oldItem == newItem } + } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/HotArticleAdapter.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/HotArticleAdapter.kt similarity index 65% rename from koin/src/main/java/in/koreatech/koin/ui/article/adapter/HotArticleAdapter.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/HotArticleAdapter.kt index e3330a7515..c0372772be 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/HotArticleAdapter.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/HotArticleAdapter.kt @@ -1,12 +1,12 @@ -package `in`.koreatech.koin.ui.article.adapter +package `in`.koreatech.koin.feature.article.ui.article.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import `in`.koreatech.koin.databinding.ItemHotArticleHeaderBinding -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState +import `in`.koreatech.koin.feature.article.databinding.ItemHotArticleHeaderBinding +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState class HotArticleAdapter( private val onClick: (ArticleHeaderState) -> Unit @@ -40,21 +40,20 @@ class HotArticleAdapter( } companion object { - private val diffUtil = - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState - ): Boolean { - return oldItem.id == newItem.id - } + private val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ArticleHeaderState, + newItem: ArticleHeaderState + ): Boolean { + return oldItem.id == newItem.id + } - override fun areContentsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState - ): Boolean { - return oldItem == newItem - } + override fun areContentsTheSame( + oldItem: ArticleHeaderState, + newItem: ArticleHeaderState + ): Boolean { + return oldItem == newItem } + } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/RecentSearchedHistoryAdapter.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/RecentSearchedHistoryAdapter.kt similarity index 74% rename from koin/src/main/java/in/koreatech/koin/ui/article/adapter/RecentSearchedHistoryAdapter.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/RecentSearchedHistoryAdapter.kt index a5318042c5..48854c7f11 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/adapter/RecentSearchedHistoryAdapter.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/adapter/RecentSearchedHistoryAdapter.kt @@ -1,11 +1,11 @@ -package `in`.koreatech.koin.ui.article.adapter +package `in`.koreatech.koin.feature.article.ui.article.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import `in`.koreatech.koin.databinding.ItemRecentSearchedHistoryBinding +import `in`.koreatech.koin.feature.article.databinding.ItemRecentSearchedHistoryBinding class RecentSearchedHistoryAdapter( private val onSearchHistoryClicked: (String) -> Unit, @@ -41,15 +41,14 @@ class RecentSearchedHistoryAdapter( } companion object { - private val diffUtil = - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { - return oldItem == newItem - } + private val diffUtil = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem + } - override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { - return oldItem == newItem - } + override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem } + } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailFragment.kt similarity index 89% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailFragment.kt index e0b6ad9f6a..1bb8dc871c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.ui.article.detail import android.app.DownloadManager import android.content.Intent @@ -18,7 +18,6 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.WebViewActivity import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction @@ -30,15 +29,17 @@ import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.core.util.withLoading import `in`.koreatech.koin.core.webview.loadKoreatechHtml import `in`.koreatech.koin.core.webview.setOnImageClickListener -import `in`.koreatech.koin.databinding.FragmentArticleDetailBinding import `in`.koreatech.koin.domain.util.DateFormatUtil -import `in`.koreatech.koin.domain.util.TimeUtil -import `in`.koreatech.koin.ui.article.adapter.AttachmentAdapter -import `in`.koreatech.koin.ui.article.adapter.HotArticleAdapter -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState -import `in`.koreatech.koin.ui.article.state.ArticleState -import `in`.koreatech.koin.ui.article.state.AttachmentState -import `in`.koreatech.koin.ui.article.viewmodel.ArticleDetailViewModel +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.databinding.FragmentArticleDetailBinding +import `in`.koreatech.koin.feature.article.enums.LinkType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.ui.article.adapter.AttachmentAdapter +import `in`.koreatech.koin.feature.article.ui.article.adapter.HotArticleAdapter +import `in`.koreatech.koin.feature.article.ui.article.state.ArticleState +import `in`.koreatech.koin.feature.article.ui.article.state.AttachmentState +import java.time.ZoneId +import java.util.Date import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -64,7 +65,7 @@ class ArticleDetailFragment : Fragment() { } private val viewModel: ArticleDetailViewModel by viewModels { - ArticleDetailViewModel.provideFactory( + ArticleDetailViewModel.Companion.provideFactory( articleDetailViewModelFactory, requireArguments().getInt(ARTICLE_ID), requireArguments().getInt(NAVIGATED_BOARD_ID) @@ -210,12 +211,11 @@ class ArticleDetailFragment : Fragment() { textViewArticleTitle.text = article.header.title textViewArticleAuthor.text = article.header.author try { - textViewArticleDate.text = - TextUtils.concat( - DateFormatUtil.getSimpleMonthAndDay(article.header.registeredAt), - " ", - DateFormatUtil.getDayOfWeek(TimeUtil.stringToDateYYYYMMDD(article.header.registeredAt)) - ) + textViewArticleDate.text = TextUtils.concat( + DateFormatUtil.getSimpleMonthAndDay(article.header.registeredAt), + " ", + DateFormatUtil.getDayOfWeek(Date.from(article.header.registeredAt.atStartOfDay(ZoneId.systemDefault()).toInstant())) + ) } catch (_: Exception) { } textViewArticleViewCount.text = article.header.viewCount.toString() @@ -226,11 +226,10 @@ class ArticleDetailFragment : Fragment() { // binding.htmlView.setHtml(article.content) binding.webView.apply { setOnImageClickListener(requireActivity()) { - val dialog = - ImageZoomableDialog(requireActivity(), it).apply { - initialScale = 0.94f - cancelableOnTouchOutside = false - } + val dialog = ImageZoomableDialog(requireActivity(), it).apply { + initialScale = 0.94f + cancelableOnTouchOutside = false + } dialog.show() } }.loadKoreatechHtml(requireContext(), article.content) diff --git a/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailViewModel.kt new file mode 100644 index 0000000000..cf4bd37eb7 --- /dev/null +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/detail/ArticleDetailViewModel.kt @@ -0,0 +1,99 @@ +package `in`.koreatech.koin.feature.article.ui.article.detail + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import `in`.koreatech.koin.core.viewmodel.BaseViewModel +import `in`.koreatech.koin.domain.repository.ArticleRepository +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.model.toArticleHeaderState +import `in`.koreatech.koin.feature.article.ui.article.state.ArticleState +import `in`.koreatech.koin.feature.article.ui.article.state.toArticleState +import java.time.LocalDate +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +class ArticleDetailViewModel @AssistedInject constructor( + @Assisted("articleId") articleId: Int, + @Assisted("navigatedBoardId") val navigatedBoardId: Int, + private val articleRepository: ArticleRepository +) : BaseViewModel() { + val article: StateFlow = articleRepository.fetchArticle(articleId, navigatedBoardId) + .onStart { + _isLoading.value = true + }.map { + it.toArticleState() + }.onEach { + _isLoading.value = false + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = ArticleState( + header = ArticleHeaderState( + id = 0, + board = ArticleBoardType.ALL, + title = "", + author = "", + viewCount = 0, + registeredAt = LocalDate.now(), + updatedAt = "" + ), + content = "", + prevArticleId = null, + nextArticleId = null, + attachments = listOf(), + url = "" + ) + ) + + val hotArticles: StateFlow> = articleRepository.fetchHotArticleHeaders() + .map { + var doesHotContainsThis = false + it.filterIndexed { index, hotArticleHeader -> + if (articleId == hotArticleHeader.id) { + doesHotContainsThis = true + } + articleId != hotArticleHeader.id && index < (HOT_ARTICLE_COUNT + if (doesHotContainsThis) 1 else 0) + }.map { it.toArticleHeaderState() } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = listOf() + ) + + fun setIsLoading(isLoading: Boolean) { + _isLoading.value = isLoading + } + + @AssistedFactory + interface Factory { + fun create( + @Assisted("articleId") articleId: Int, + @Assisted("navigatedBoardId") navigatedBoardId: Int + ): ArticleDetailViewModel + } + + companion object { + const val HOT_ARTICLE_COUNT = 4 + + fun provideFactory( + assistedFactory: Factory, + article: Int, + boardId: Int + ): ViewModelProvider.Factory { + return object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return assistedFactory.create(article, boardId) as T + } + } + } + } +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordFragment.kt similarity index 92% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordFragment.kt index 9ad13d68b2..31a36aa299 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.ui.article.keyword import android.os.Bundle import android.view.LayoutInflater @@ -16,7 +16,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger @@ -26,13 +25,9 @@ import `in`.koreatech.koin.core.navigation.Navigator import `in`.koreatech.koin.core.permission.checkNotificationPermission import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.core.util.SnackbarUtil -import `in`.koreatech.koin.databinding.FragmentArticleKeywordBinding import `in`.koreatech.koin.domain.model.notification.SubscribesType -import `in`.koreatech.koin.ui.article.viewmodel.ArticleKeywordViewModel -import `in`.koreatech.koin.ui.article.viewmodel.KeywordAddUiState -import `in`.koreatech.koin.ui.article.viewmodel.KeywordInputUiState -import `in`.koreatech.koin.ui.notification.viewmodel.NotificationUiState -import `in`.koreatech.koin.ui.notification.viewmodel.NotificationViewModel +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.databinding.FragmentArticleKeywordBinding import javax.inject.Inject import kotlinx.coroutines.launch @@ -42,7 +37,6 @@ class ArticleKeywordFragment : Fragment() { private val binding get() = _binding!! private val viewModel by viewModels() - private val notificationViewModel by viewModels() @Inject lateinit var navigator: Navigator @@ -60,7 +54,10 @@ class ArticleKeywordFragment : Fragment() { AnalyticsConstant.Label.LOGIN_PROMPT, "키워드 알림 팝업" ) - val intent = navigator.navigateToSignIn(this.requireContext(), redirectUrl = "koin://article/activity?fragment=article_keyword") + val intent = navigator.navigateToSignIn( + this.requireContext(), + redirectUrl = "koin://article/activity?fragment=article_keyword" + ) it.dismiss() startActivity(intent) }, @@ -78,7 +75,7 @@ class ArticleKeywordFragment : Fragment() { if (_binding == null) { _binding = FragmentArticleKeywordBinding.inflate(inflater, container, false) binding.textViewMaxKeywordCount.text = - ArticleKeywordViewModel.MAX_KEYWORD_COUNT.toString() + ArticleKeywordViewModel.Companion.MAX_KEYWORD_COUNT.toString() binding.buttonAddKeyword.setOnClickListener { viewModel.addKeyword(binding.textInputSearch.text.toString()) } @@ -159,7 +156,7 @@ class ArticleKeywordFragment : Fragment() { binding.chipGroupSuggestionKeywords.removeAllViews() suggests.forEach { keyword -> binding.run { - if (chipGroupSuggestionKeywords.childCount >= ArticleKeywordViewModel.MAX_SUGGEST_KEYWORD_COUNT) { + if (chipGroupSuggestionKeywords.childCount >= ArticleKeywordViewModel.Companion.MAX_SUGGEST_KEYWORD_COUNT) { return@forEach } @@ -325,7 +322,7 @@ class ArticleKeywordFragment : Fragment() { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.user.collect { user -> if (user.isAnonymous.not()) { - notificationViewModel.getPermissionInfo() + viewModel.getPermissionInfo() } } } @@ -350,20 +347,20 @@ class ArticleKeywordFragment : Fragment() { AnalyticsConstant.Label.KEYWORD_NOTIFICATION, "on" ) - notificationViewModel.updateSubscription(SubscribesType.ARTICLE_KEYWORD) + viewModel.updateSubscription(SubscribesType.ARTICLE_KEYWORD) } else { EventLogger.logClickEvent( EventAction.CAMPUS, AnalyticsConstant.Label.KEYWORD_NOTIFICATION, "off" ) - notificationViewModel.deleteSubscription(SubscribesType.ARTICLE_KEYWORD) + viewModel.deleteSubscription(SubscribesType.ARTICLE_KEYWORD) } } viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - notificationViewModel.notificationUiState.collect { uiState -> + viewModel.notificationUiState.collect { uiState -> if (uiState is NotificationUiState.Success) { uiState.notificationPermissionInfo.subscribes.forEach { if (it.type == SubscribesType.ARTICLE_KEYWORD) { diff --git a/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordViewModel.kt new file mode 100644 index 0000000000..82b11d0e69 --- /dev/null +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/keyword/ArticleKeywordViewModel.kt @@ -0,0 +1,213 @@ +package `in`.koreatech.koin.feature.article.ui.article.keyword + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.core.viewmodel.BaseViewModel +import `in`.koreatech.koin.domain.model.notification.NotificationPermissionInfo +import `in`.koreatech.koin.domain.model.notification.SubscribesDetailType +import `in`.koreatech.koin.domain.model.notification.SubscribesType +import `in`.koreatech.koin.domain.model.user.User +import `in`.koreatech.koin.domain.repository.ArticleRepository +import `in`.koreatech.koin.domain.usecase.notification.DeleteNotificationSubscriptionDetailUseCase +import `in`.koreatech.koin.domain.usecase.notification.DeleteNotificationSubscriptionUseCase +import `in`.koreatech.koin.domain.usecase.notification.GetNotificationPermissionInfoUseCase +import `in`.koreatech.koin.domain.usecase.notification.UpdateNotificationSubscriptionDetailUseCase +import `in`.koreatech.koin.domain.usecase.notification.UpdateNotificationSubscriptionUseCase +import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase +import `in`.koreatech.koin.domain.util.onFailure +import `in`.koreatech.koin.domain.util.onSuccess +import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update + +@HiltViewModel +class ArticleKeywordViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, + private val articleRepository: ArticleRepository, + private val getNotificationPermissionInfoUseCase: GetNotificationPermissionInfoUseCase, + private val updateNotificationSubscriptionUseCase: UpdateNotificationSubscriptionUseCase, + private val updateNotificationSubscriptionDetailUseCase: UpdateNotificationSubscriptionDetailUseCase, + private val deleteNotificationSubscriptionUseCase: DeleteNotificationSubscriptionUseCase, + private val deleteNotificationSubscriptionDetailUseCase: DeleteNotificationSubscriptionDetailUseCase, + getUserStatusUseCase: GetUserStatusUseCase +) : BaseViewModel() { + private val _notificationUiState = + MutableStateFlow(NotificationUiState.Nothing) + val notificationUiState = _notificationUiState.asStateFlow() + + val user: StateFlow = getUserStatusUseCase() + .stateIn(viewModelScope, SharingStarted.Eagerly, User.Anonymous) + + val keywordInputUiState: StateFlow = savedStateHandle.getStateFlow(KEYWORD_INPUT, "").map { + if (it.isEmpty()) KeywordInputUiState.Empty else KeywordInputUiState.Valid(it) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = KeywordInputUiState.Empty + ) + + val myKeywords: StateFlow> = articleRepository.fetchMyKeyword() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = listOf() + ) + + private val _keywordAddUiState = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val keywordAddUiState: SharedFlow = _keywordAddUiState + .shareIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000) + ) + + val suggestedKeywords: StateFlow> = articleRepository.fetchKeywordSuggestions() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = listOf() + ) + + fun addKeyword(keyword: String) { + val trimmedKeyword = keyword.trim().ifEmpty { + _keywordAddUiState.tryEmit(KeywordAddUiState.RequireInput) + return + } + if (myKeywords.value.size >= MAX_KEYWORD_COUNT) { + _keywordAddUiState.tryEmit(KeywordAddUiState.LimitExceeded) + return + } + + if (trimmedKeyword.length > MAX_KEYWORD_LENGTH || trimmedKeyword.length < MIN_KEYWORD_LENGTH) { + _keywordAddUiState.tryEmit(KeywordAddUiState.InvalidLength) + return + } + + if (myKeywords.value.contains(trimmedKeyword)) { + _keywordAddUiState.tryEmit(KeywordAddUiState.AlreadyExist) + return + } + + if (trimmedKeywordRegex.containsMatchIn(trimmedKeyword)) { // jusang-regex-opt + _keywordAddUiState.tryEmit(KeywordAddUiState.BlankNotAllowed) + return + } + + articleRepository.saveKeyword(trimmedKeyword).onStart { + _keywordAddUiState.emit(KeywordAddUiState.Loading) + }.onEach { + _keywordAddUiState.emit(KeywordAddUiState.Success(trimmedKeyword)) + }.catch { + _keywordAddUiState.emit(KeywordAddUiState.Error) + }.launchIn(viewModelScope) + } + + fun deleteKeyword(keyword: String) { + articleRepository.deleteKeyword(keyword).onEach { + _keywordAddUiState.emit(KeywordAddUiState.Success(keyword)) + }.catch { + _keywordAddUiState.emit(KeywordAddUiState.Error) + }.launchIn(viewModelScope) + } + + fun onKeywordInputChanged(keyword: String) { + savedStateHandle[KEYWORD_INPUT] = keyword + } + + fun getPermissionInfo() { + viewModelScope.launchWithLoading { + getNotificationPermissionInfoUseCase().onSuccess { info -> + _notificationUiState.update { + NotificationUiState.Success(info) + } + }.onFailure { + _notificationUiState.update { NotificationUiState.Failed } + } + } + } + + fun updateSubscription(type: SubscribesType) { + viewModelScope.launchWithLoading { + updateNotificationSubscriptionUseCase(type) + } + } + + fun updateSubscriptionDetail(type: SubscribesDetailType) { + viewModelScope.launchWithLoading { + updateNotificationSubscriptionDetailUseCase(type) + } + } + + fun deleteSubscription(type: SubscribesType) { + viewModelScope.launchWithLoading { + deleteNotificationSubscriptionUseCase(type) + } + } + + fun deleteSubscriptionDetail(type: SubscribesDetailType) { + viewModelScope.launchWithLoading { + deleteNotificationSubscriptionDetailUseCase(type) + } + } + + companion object { + const val MAX_KEYWORD_COUNT = 10 + const val MAX_SUGGEST_KEYWORD_COUNT = 5 + const val MAX_KEYWORD_LENGTH = 20 + const val MIN_KEYWORD_LENGTH = 2 + const val KEYWORD_INPUT = "keyword_input" + val trimmedKeywordRegex = Regex("""\s+""") + } +} + +sealed interface KeywordAddUiState { + data object Nothing : KeywordAddUiState + + data object Loading : KeywordAddUiState + + data class Success(val keyword: String) : KeywordAddUiState + + data object AlreadyExist : KeywordAddUiState + + data object LimitExceeded : KeywordAddUiState + + data object BlankNotAllowed : KeywordAddUiState + + data object InvalidLength : KeywordAddUiState + + data object RequireInput : KeywordAddUiState + + data object Error : KeywordAddUiState +} + +sealed interface KeywordInputUiState { + data object Empty : KeywordInputUiState + + data class Valid(val keyword: String) : KeywordInputUiState +} + +sealed class NotificationUiState { + data class Success( + val notificationPermissionInfo: NotificationPermissionInfo + ) : NotificationUiState() + + data object Failed : NotificationUiState() + + data object Nothing : NotificationUiState() +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListFragment.kt similarity index 80% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListFragment.kt index 7484970a77..47c8f82e32 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.ui.article.list import android.os.Bundle import android.view.LayoutInflater @@ -11,16 +11,17 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.tabs.TabLayout import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.core.util.withLoading -import `in`.koreatech.koin.databinding.FragmentArticleListBinding -import `in`.koreatech.koin.ui.article.ArticleActivity.Companion.START_BOARD -import `in`.koreatech.koin.ui.article.lostandfound.ArticleListLostAndFoundFragment -import `in`.koreatech.koin.ui.article.viewmodel.ArticleListViewModel +import `in`.koreatech.koin.feature.article.ArticleActivity.Companion.START_BOARD +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.databinding.FragmentArticleListBinding +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.ui.article.notice.ArticleListNoticeFragment +import `in`.koreatech.koin.feature.article.ui.lostandfound.fragment.ArticleListLostAndFoundFragment import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -29,23 +30,22 @@ class ArticleListFragment : Fragment() { private var _binding: FragmentArticleListBinding? = null private val binding get() = _binding!! - private val onTabSelectedListener = - object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab?) { - tab?.let { - EventLogger.logClickEvent( - EventAction.CAMPUS, - AnalyticsConstant.Label.NOTICE_TAB, - it.text.toString() - ) - viewModel.setCurrentBoard(ArticleBoardType.entries[it.position]) - } + private val onTabSelectedListener = object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + tab?.let { + EventLogger.logClickEvent( + EventAction.CAMPUS, + AnalyticsConstant.Label.NOTICE_TAB, + it.text.toString() + ) + viewModel.setCurrentBoard(ArticleBoardType.entries[it.position]) } + } - override fun onTabUnselected(tab: TabLayout.Tab?) {} + override fun onTabUnselected(tab: TabLayout.Tab?) {} - override fun onTabReselected(tab: TabLayout.Tab?) {} - } + override fun onTabReselected(tab: TabLayout.Tab?) {} + } private val viewModel by viewModels() @@ -99,6 +99,7 @@ class ArticleListFragment : Fragment() { .replace(R.id.frame_layout_article_list, articleListNoticeFragment) .commit() } + ArticleBoardType.LOSTANDFOUND -> { childFragmentManager.beginTransaction() .replace( diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListViewModel.kt similarity index 83% rename from koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListViewModel.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListViewModel.kt index fef240ab74..b9a71d611d 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListViewModel.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/list/ArticleListViewModel.kt @@ -1,9 +1,9 @@ -package `in`.koreatech.koin.ui.article.viewmodel +package `in`.koreatech.koin.feature.article.ui.article.list import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel -import `in`.koreatech.koin.ui.article.ArticleBoardType +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType import javax.inject.Inject @HiltViewModel diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListNoticeFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeFragment.kt similarity index 90% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleListNoticeFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeFragment.kt index 3e99e33a74..c2f5490114 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListNoticeFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.ui.article.notice import android.graphics.Canvas import android.graphics.Paint @@ -23,20 +23,22 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.analytics.EventUtils +import `in`.koreatech.koin.core.onboarding.ArrowDirection import `in`.koreatech.koin.core.onboarding.OnboardingManager +import `in`.koreatech.koin.core.onboarding.OnboardingType import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.core.util.withLoading -import `in`.koreatech.koin.databinding.FragmentArticleListNoticeBinding -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.ARTICLE_ID -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID -import `in`.koreatech.koin.ui.article.adapter.ArticleAdapter -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState -import `in`.koreatech.koin.ui.article.viewmodel.ArticleListNoticeViewModel +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.databinding.FragmentArticleListNoticeBinding +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.ui.article.adapter.ArticleAdapter +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.ARTICLE_ID +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -67,14 +69,13 @@ class ArticleListNoticeFragment : Fragment() { initArgument() initArticleRecyclerView() initPageButtonSelectedListener() - pageChips = - arrayListOf( - binding.chipPage1, - binding.chipPage2, - binding.chipPage3, - binding.chipPage4, - binding.chipPage5 - ) + pageChips = arrayListOf( + binding.chipPage1, + binding.chipPage2, + binding.chipPage3, + binding.chipPage4, + binding.chipPage5 + ) binding.textViewNextPage.setOnClickListener { viewModel.setCurrentPage(viewModel.currentPage.value + 1) } @@ -132,10 +133,10 @@ class ArticleListNoticeFragment : Fragment() { private fun initKeywordTooltip() { with(onboardingManager) { showOnboardingTooltipIfNeeded( - type = `in`.koreatech.koin.core.onboarding.OnboardingType.ARTICLE_KEYWORD, + type = OnboardingType.ARTICLE_KEYWORD, view = binding.imageViewToKeywordAddPage, arrowPosition = 0.135f, - arrowDirection = `in`.koreatech.koin.core.onboarding.ArrowDirection.TOP + arrowDirection = ArrowDirection.TOP ) } } @@ -254,11 +255,10 @@ class ArticleListNoticeFragment : Fragment() { private fun addKeywordChip(keywords: List) { keywords.forEach { keyword -> if (binding.chipGroupMyKeywords.children.any { - (it as? Chip)?.text == - TextUtils.concat( - "#", - keyword - ) + (it as? Chip)?.text == TextUtils.concat( + "#", + keyword + ) }.not() ) { binding.chipGroupMyKeywords.addView( @@ -279,12 +279,11 @@ class ArticleListNoticeFragment : Fragment() { } private fun createChip(text: String, isCheckable: Boolean, onChipClicked: () -> Unit): Chip? { - val chip = - layoutInflater.inflate( - R.layout.chip_layout, - binding.chipGroupMyKeywords, - false - ) as? Chip + val chip = layoutInflater.inflate( + R.layout.chip_layout, + binding.chipGroupMyKeywords, + false + ) as? Chip return chip?.apply { id = View.generateViewId() this.isCheckable = isCheckable diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListNoticeViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeViewModel.kt similarity index 57% rename from koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListNoticeViewModel.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeViewModel.kt index f22ddb62f6..b0599a355e 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleListNoticeViewModel.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/notice/ArticleListNoticeViewModel.kt @@ -1,13 +1,13 @@ -package `in`.koreatech.koin.ui.article.viewmodel +package `in`.koreatech.koin.feature.article.ui.article.notice import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.repository.ArticleRepository -import `in`.koreatech.koin.ui.article.ArticleBoardType -import `in`.koreatech.koin.ui.article.state.ArticlePaginationState -import `in`.koreatech.koin.ui.article.state.toArticlePaginationState +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.ui.article.state.ArticlePaginationState +import `in`.koreatech.koin.feature.article.ui.article.state.toArticlePaginationState import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -27,46 +27,43 @@ class ArticleListNoticeViewModel @Inject constructor( val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1) val selectedKeyword = savedStateHandle.getStateFlow(SELECTED_KEYWORD, "") - val pageNumbers = - savedStateHandle.getStateFlow( - PAGE_NUMBERS, - IntArray(PAGE_NUMBER_COUNT) - ) // 값이 0일 경우 존재하지 않는 페이지 + val pageNumbers = savedStateHandle.getStateFlow( + PAGE_NUMBERS, + IntArray(PAGE_NUMBER_COUNT) + ) // 값이 0일 경우 존재하지 않는 페이지 - val myKeywords: StateFlow> = - articleRepository.fetchMyKeyword() - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = listOf() - ) - - val articlePagination: StateFlow = - combine(currentBoard, currentPage, selectedKeyword) { board, page, query -> - _isLoading.value = true - if (query.isEmpty()) { - articleRepository.fetchArticlePagination(board.id, page, ARTICLES_PER_PAGE) - } else { - articleRepository.fetchSearchedArticles( - query, - board.id, - page, - ARTICLES_PER_PAGE - ) - } - }.debounce(10).flatMapLatest { - it.mapLatest { articlePagination -> - articlePagination.toArticlePaginationState() - } - }.onEach { - _isLoading.value = false - calculatePageNumber(it.totalPage) - }.stateIn( + val myKeywords: StateFlow> = articleRepository.fetchMyKeyword() + .stateIn( scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000), - initialValue = ArticlePaginationState(emptyList(), 0, 0, 5, 1) + started = SharingStarted.WhileSubscribed(5_000), + initialValue = listOf() ) + val articlePagination: StateFlow = combine(currentBoard, currentPage, selectedKeyword) { board, page, query -> + _isLoading.value = true + if (query.isEmpty()) { + articleRepository.fetchArticlePagination(board.id, page, ARTICLES_PER_PAGE) + } else { + articleRepository.fetchSearchedArticles( + query, + board.id, + page, + ARTICLES_PER_PAGE + ) + } + }.debounce(10).flatMapLatest { + it.mapLatest { articlePagination -> + articlePagination.toArticlePaginationState() + } + }.onEach { + _isLoading.value = false + calculatePageNumber(it.totalPage) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = ArticlePaginationState(emptyList(), 0, 0, 5, 1) + ) + fun setCurrentBoard(board: ArticleBoardType) { if (currentBoard.value == board) return savedStateHandle[BOARD_TYPE] = board @@ -86,8 +83,7 @@ class ArticleListNoticeViewModel @Inject constructor( private fun calculatePageNumber(totalPage: Int) { val newPageNumbers = pageNumbers.value.copyOf() repeat(PAGE_NUMBER_COUNT) { index -> - val pageNumber = - ((currentPage.value - 1) / PAGE_NUMBER_COUNT) * PAGE_NUMBER_COUNT + index + 1 + val pageNumber = ((currentPage.value - 1) / PAGE_NUMBER_COUNT) * PAGE_NUMBER_COUNT + index + 1 if (pageNumber <= totalPage) { newPageNumbers[index] = pageNumber } else { diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchFragment.kt similarity index 92% rename from koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchFragment.kt index dcba2891ae..7f32c039e5 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.ui.article.search import android.os.Bundle import android.view.LayoutInflater @@ -20,21 +20,20 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.core.util.SnackbarUtil import `in`.koreatech.koin.core.util.withLoading -import `in`.koreatech.koin.databinding.FragmentArticleSearchBinding -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.ARTICLE_ID -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID -import `in`.koreatech.koin.ui.article.adapter.ArticleAdapter -import `in`.koreatech.koin.ui.article.adapter.RecentSearchedHistoryAdapter -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState -import `in`.koreatech.koin.ui.article.viewmodel.ArticleSearchViewModel -import `in`.koreatech.koin.ui.article.viewmodel.SearchUiState +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.databinding.FragmentArticleSearchBinding +import `in`.koreatech.koin.feature.article.enums.ArticleBoardType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.ui.article.adapter.ArticleAdapter +import `in`.koreatech.koin.feature.article.ui.article.adapter.RecentSearchedHistoryAdapter +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.ARTICLE_ID +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID import kotlinx.coroutines.launch @AndroidEntryPoint @@ -156,7 +155,7 @@ class ArticleSearchFragment : Fragment() { is SearchUiState.Empty -> { binding.frameLayoutSearchResult.visibility = View.VISIBLE - searchResultAdapter.submitList(emptyList()) + searchResultAdapter.submitList(emptyList()) binding.textViewSearchResultEmpty.visibility = View.VISIBLE } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleSearchViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchViewModel.kt similarity index 74% rename from koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleSearchViewModel.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchViewModel.kt index a96848793f..c4d03ddbb9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleSearchViewModel.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/search/ArticleSearchViewModel.kt @@ -1,12 +1,12 @@ -package `in`.koreatech.koin.ui.article.viewmodel +package `in`.koreatech.koin.feature.article.ui.article.search import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.repository.ArticleRepository -import `in`.koreatech.koin.ui.article.state.ArticlePaginationState -import `in`.koreatech.koin.ui.article.state.toArticlePaginationState +import `in`.koreatech.koin.feature.article.ui.article.state.ArticlePaginationState +import `in`.koreatech.koin.feature.article.ui.article.state.toArticlePaginationState import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow @@ -29,36 +29,32 @@ class ArticleSearchViewModel @Inject constructor( ) : BaseViewModel() { val query = savedStateHandle.getStateFlow(SEARCH_INPUT, "") - val searchHistory: StateFlow> = - articleRepository.fetchSearchHistory() - .map { - it.take(MAX_SEARCH_HISTORY_COUNT) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = emptyList() - ) - - private val _searchResultUiState = - MutableSharedFlow( - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - val searchResultUiState: SharedFlow = - _searchResultUiState.shareIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000) - ) - - val mostSearchedKeywords: StateFlow> = - articleRepository.fetchMostSearchedKeywords( - MOST_SEARCHED_KEYWORD_COUNT - ).stateIn( + val searchHistory: StateFlow> = articleRepository.fetchSearchHistory() + .map { + it.take(MAX_SEARCH_HISTORY_COUNT) + }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) + private val _searchResultUiState = MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val searchResultUiState: SharedFlow = _searchResultUiState.shareIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000) + ) + + val mostSearchedKeywords: StateFlow> = articleRepository.fetchMostSearchedKeywords( + MOST_SEARCHED_KEYWORD_COUNT + ).stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList() + ) + fun onSearchInputChanged(query: String) { savedStateHandle[SEARCH_INPUT] = query } diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticlePaginationState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticlePaginationState.kt similarity index 72% rename from koin/src/main/java/in/koreatech/koin/ui/article/state/ArticlePaginationState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticlePaginationState.kt index 3ae59bbb97..cef0d27422 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticlePaginationState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticlePaginationState.kt @@ -1,6 +1,8 @@ -package `in`.koreatech.koin.ui.article.state +package `in`.koreatech.koin.feature.article.ui.article.state import `in`.koreatech.koin.domain.model.article.ArticlePagination +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.model.toArticleHeaderState data class ArticlePaginationState( val articles: List, diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticleState.kt similarity index 72% rename from koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticleState.kt index c575f2a07e..b9ebc06261 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/ArticleState.kt @@ -1,6 +1,8 @@ -package `in`.koreatech.koin.ui.article.state +package `in`.koreatech.koin.feature.article.ui.article.state import `in`.koreatech.koin.domain.model.article.Article +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.model.toArticleHeaderState data class ArticleState( val header: ArticleHeaderState, diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/state/AttachmentState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/AttachmentState.kt similarity index 93% rename from koin/src/main/java/in/koreatech/koin/ui/article/state/AttachmentState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/AttachmentState.kt index 759eb74dc7..652234c023 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/state/AttachmentState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/AttachmentState.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.state +package `in`.koreatech.koin.feature.article.ui.article.state import `in`.koreatech.koin.core.util.RegexPatterns import `in`.koreatech.koin.domain.model.article.Attachment diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/state/HtmlElement.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/HtmlElement.kt similarity index 93% rename from koin/src/main/java/in/koreatech/koin/ui/article/state/HtmlElement.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/HtmlElement.kt index 75ca84bed7..cebc8883a3 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/state/HtmlElement.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/article/state/HtmlElement.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.state +package `in`.koreatech.koin.feature.article.ui.article.state import android.os.Parcelable import `in`.koreatech.koin.domain.model.article.html.CssAttribute diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetail.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetail.kt similarity index 86% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetail.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetail.kt index 436f91b807..59bd128e2c 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetail.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetail.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail import android.content.Context import android.content.Intent @@ -22,15 +22,15 @@ import androidx.hilt.navigation.compose.hiltViewModel import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.HotArticle -import `in`.koreatech.koin.feature.lostandfound.component.HotArticleData -import `in`.koreatech.koin.feature.lostandfound.component.LoadingDialog -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.ui.detail.component.DetailButtonGroup -import `in`.koreatech.koin.feature.lostandfound.ui.detail.component.DetailContent -import `in`.koreatech.koin.feature.lostandfound.ui.detail.component.DetailHeader -import `in`.koreatech.koin.feature.lostandfound.util.findActivity +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.HotArticle +import `in`.koreatech.koin.feature.article.component.HotArticleData +import `in`.koreatech.koin.feature.article.component.LoadingDialog +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component.DetailButtonGroup +import `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component.DetailContent +import `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component.DetailHeader +import `in`.koreatech.koin.feature.article.util.findActivity import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -153,10 +153,9 @@ private fun handleSideEffect( LostAndFoundDetailSideEffect.DeletedArticle -> { context.findActivity()?.finish() - val intent = - Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("koin://article/activity?fragment=article_lost_and_found") - } + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse("koin://article/activity?fragment=article_lost_and_found") + } context.startActivity(intent) Toast.makeText( context, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailSideEffect.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailSideEffect.kt similarity index 85% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailSideEffect.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailSideEffect.kt index f5a96821e0..28d71d96ed 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailSideEffect.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailSideEffect.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail sealed class LostAndFoundDetailSideEffect { // data class FetchDetail(val id: Int) : LostAndFoundDetailSideEffect() diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailState.kt similarity index 85% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailState.kt index 3a7535f1e2..6c45c20bd4 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailState.kt @@ -1,11 +1,11 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail import android.net.Uri import android.os.Parcelable import `in`.koreatech.koin.domain.model.article.ArticleLostAndFound -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.model.ArticleHeaderState +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.model.ArticleHeaderState import java.time.LocalDate import kotlinx.parcelize.Parcelize diff --git a/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailViewModel.kt new file mode 100644 index 0000000000..b542995c9e --- /dev/null +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/LostAndFoundDetailViewModel.kt @@ -0,0 +1,140 @@ +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail + +import android.webkit.URLUtil +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.model.user.User +import `in`.koreatech.koin.domain.usecase.article.lostandfound.DeleteArticleLostAndFoundUseCase +import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchHotArticlesUseCase +import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchLostAndFoundArticleUseCase +import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase +import `in`.koreatech.koin.feature.article.model.toArticleHeaderState +import javax.inject.Inject +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container +import retrofit2.HttpException + +@HiltViewModel +class LostAndFoundDetailViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val fetchLostAndFoundArticleUseCase: FetchLostAndFoundArticleUseCase, + private val fetchHotArticlesUseCase: FetchHotArticlesUseCase, + private val deleteArticleLostAndFoundUseCase: DeleteArticleLostAndFoundUseCase, + private val getUserStatusUseCase: GetUserStatusUseCase +) : ViewModel(), ContainerHost { + override val container = container(LostAndFoundDetailState(), savedStateHandle) { + val articleId = savedStateHandle.get(ARTICLE_ID) + checkNotNull(articleId) + fetchLostAndFoundDetail(articleId) + } + + init { + initUserInfo() + fetchHotArticles() + } + + private fun initUserInfo() = viewModelScope.launch { + getUserStatusUseCase().collectLatest { + intent { + when (it) { + is User.Student -> reduce { + state.copy( + isLoggedIn = true, + currentLoggedInUser = it.nickname ?: "" + ) + } + is User.General -> reduce { + state.copy( + isLoggedIn = true, + currentLoggedInUser = it.nickname ?: "" + ) + } + is User.Anonymous -> reduce { + state.copy(isLoggedIn = false) + } + } + } + } + } + + fun fetchLostAndFoundDetail(articleId: Int) = viewModelScope.launch { + intent { + reduce { + state.copy( + isLoading = true + ) + } + fetchLostAndFoundArticleUseCase(articleId).catch { + if (it is HttpException && it.code() == 404) { + postSideEffect(LostAndFoundDetailSideEffect.DeletedArticle) + } + }.map { + it.toLostAndFoundDetailState() + }.collectLatest { article -> + reduce { + state.copy( + lostOrFound = article.lostOrFound, + id = article.id, + category = article.category, + foundPlace = article.foundPlace, + foundDate = article.foundDate, + content = article.content, + author = article.author, + images = article.images?.filter { URLUtil.isValidUrl(it.toString()) }, + registeredAt = article.registeredAt, + updatedAt = article.updatedAt, + isWriterCouncil = article.isWriterCouncil, + isMine = state.currentLoggedInUser == article.author, + isAuthorWithdraw = article.author == "탈퇴한 사용자", + isLoading = false + ) + } + } + } + } + + fun fetchHotArticles() = viewModelScope.launch { + intent { + fetchHotArticlesUseCase().collectLatest { + reduce { + state.copy( + hotArticles = it.filterIndexed { index, _ -> index < HOT_ARTICLE_COUNT } + .map { it.toArticleHeaderState() } + ) + } + } + } + } + + fun deleteArticle() = viewModelScope.launch { + intent { + deleteArticleLostAndFoundUseCase(state.id).onSuccess { + postSideEffect(LostAndFoundDetailSideEffect.DeleteArticle(state.id)) + }.onFailure { + postSideEffect(LostAndFoundDetailSideEffect.DeleteArticleFailed) + } + } + } + + fun setShowDeleteDialog(show: Boolean) = intent { + reduce { + state.copy( + showDeleteDialog = show + ) + } + } + + companion object { + const val HOT_ARTICLE_COUNT = 4 + const val ARTICLE_ID = "article_id" + } +} diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailButtonGroup.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailButtonGroup.kt similarity index 98% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailButtonGroup.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailButtonGroup.kt index 865593a107..5346d2299f 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailButtonGroup.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailButtonGroup.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.PaddingValues @@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun DetailButtonGroup( diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailContent.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailContent.kt similarity index 72% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailContent.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailContent.kt index 41612e8c83..059a7f667b 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailContent.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailContent.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component import android.net.Uri import android.os.Build @@ -37,7 +37,7 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.request.ImageRequest import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun DetailContent( @@ -46,36 +46,32 @@ fun DetailContent( isWriterAdmin: Boolean, modifier: Modifier = Modifier ) { - val imageLoader = - ImageLoader.Builder(LocalContext.current) - .components { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } + val imageLoader = ImageLoader.Builder(LocalContext.current) + .components { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + add(ImageDecoderDecoder.Factory()) + } else { + add(GifDecoder.Factory()) } - .build() + } + .build() Column( - modifier = - modifier + modifier = modifier .padding(vertical = 24.dp, horizontal = 24.dp) .fillMaxWidth() ) { if (imageUris != null) { - val pagerState = - rememberPagerState(pageCount = { - imageUris.size - }) + val pagerState = rememberPagerState(pageCount = { + imageUris.size + }) HorizontalPager( modifier = Modifier.fillMaxWidth(), state = pagerState ) { page -> SubcomposeAsyncImage( modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally), - model = - ImageRequest.Builder(LocalContext.current) + model = ImageRequest.Builder(LocalContext.current) .data(imageUris[page]) .crossfade(true) .build(), @@ -96,8 +92,7 @@ fun DetailContent( Spacer(modifier = Modifier.height(8.dp)) Row( - modifier = - Modifier + modifier = Modifier .padding(vertical = 12.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), @@ -105,16 +100,14 @@ fun DetailContent( ) { imageUris.indices.forEach { index -> Image( - modifier = - if (index == pagerState.currentPage) { + modifier = if (index == pagerState.currentPage) { Modifier.size(6.dp) } else { Modifier.size( 4.dp ) }, - painter = - if (index == pagerState.currentPage) { + painter = if (index == pagerState.currentPage) { painterResource(R.drawable.ic_image_page_indicator_filled) } else { painterResource(id = R.drawable.ic_image_page_indicator_not_filled) @@ -137,29 +130,27 @@ fun DetailContent( Spacer(modifier = Modifier.height(24.dp)) Box( - modifier = - Modifier + modifier = Modifier .clip(KoinTheme.shapes.medium) .fillMaxWidth() .background(KoinTheme.colors.neutral100) .padding(vertical = 16.dp, horizontal = 18.dp), contentAlignment = Alignment.Center ) { - val infoMessage = - buildAnnotatedString { - withStyle(KoinTheme.typography.regular12.toSpanStyle()) { - append(stringResource(R.string.detail_student_association_info_1)) - } - withStyle( - KoinTheme.typography.regular12.copy(fontWeight = FontWeight.Bold) - .toSpanStyle() - ) { - append(stringResource(R.string.detail_student_association_info_2)) - } - withStyle(KoinTheme.typography.regular12.toSpanStyle()) { - append(stringResource(R.string.detail_student_association_info_3)) - } + val infoMessage = buildAnnotatedString { + withStyle(KoinTheme.typography.regular12.toSpanStyle()) { + append(stringResource(R.string.detail_student_association_info_1)) + } + withStyle( + KoinTheme.typography.regular12.copy(fontWeight = FontWeight.Bold) + .toSpanStyle() + ) { + append(stringResource(R.string.detail_student_association_info_2)) } + withStyle(KoinTheme.typography.regular12.toSpanStyle()) { + append(stringResource(R.string.detail_student_association_info_3)) + } + } Text( text = infoMessage, textAlign = TextAlign.Center, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailDialog.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailDialog.kt similarity index 96% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailDialog.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailDialog.kt index fd26f5526b..743083ac68 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailDialog.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailDialog.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -24,7 +24,7 @@ import `in`.koreatech.koin.core.designsystem.component.button.FilledButtonColors import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButton import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButtonColors import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R /** * @see in.koreatech.koin.core.designsystem.component.dialog.ChoiceDialog @@ -52,8 +52,7 @@ fun DetailDialog( negativeButtonColors: OutlinedBoxButtonColors = OutlinedBoxButtonColors.Neutral ) { BasicAlertDialog( - modifier = - modifier + modifier = modifier .fillMaxWidth() .wrapContentHeight() .background( diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailHeader.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailHeader.kt similarity index 72% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailHeader.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailHeader.kt index 90b1f8d65a..0a17a7664a 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/component/DetailHeader.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/detail/component/DetailHeader.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.detail.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,10 +12,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.component.LostItemTypeChip -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.util.getKoreanDayOfWeekShortName +import `in`.koreatech.koin.feature.article.component.LostItemTypeChip +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.util.getKoreanDayOfWeekShortName import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -30,19 +30,17 @@ fun DetailHeader( modifier: Modifier = Modifier ) { val registeredAtFormatType = DateTimeFormatter.ofPattern("MM.dd") - val convertedRegisteredAt = - remember(key1 = registeredAt) { "${registeredAt.format(registeredAtFormatType)} ${registeredAt.getKoreanDayOfWeekShortName()}" } + val convertedRegisteredAt = remember(key1 = registeredAt) { "${registeredAt.format(registeredAtFormatType)} ${registeredAt.getKoreanDayOfWeekShortName()}" } val foundDateFormatType = DateTimeFormatter.ofPattern("yy.MM.dd") - val headerText = - remember(key1 = foundPlace, key2 = foundDate) { - "${ - foundPlace.replace( - "\n", - " " - ) - } | ${foundDate.format(foundDateFormatType)}" - } + val headerText = remember(key1 = foundPlace, key2 = foundDate) { + "${ + foundPlace.replace( + "\n", + " " + ) + } | ${foundDate.format(foundDateFormatType)}" + } Column( modifier = modifier.padding(vertical = 12.dp, horizontal = 24.dp) diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleListLostAndFoundFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleListLostAndFoundFragment.kt similarity index 89% rename from koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleListLostAndFoundFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleListLostAndFoundFragment.kt index e6897431a8..1cf16b7aba 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleListLostAndFoundFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleListLostAndFoundFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.fragment import android.os.Bundle import android.view.LayoutInflater @@ -10,10 +10,10 @@ import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.navigation.Navigator -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.LostAndFoundList -import `in`.koreatech.koin.feature.lostandfound.ui.write.LostAndFoundWriteArticleViewModel.Companion.LOST_OR_FOUND_TYPE +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.LostAndFoundList +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.LostAndFoundWriteArticleViewModel.Companion.LOST_OR_FOUND_TYPE import javax.inject.Inject @AndroidEntryPoint diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundDetailFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundDetailFragment.kt similarity index 75% rename from koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundDetailFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundDetailFragment.kt index 08b0c6bd01..adf43e760f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundDetailFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundDetailFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.fragment import android.content.Intent import android.os.Bundle @@ -10,16 +10,22 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger -import `in`.koreatech.koin.feature.chat.ui.room.ChatRoomActivity -import `in`.koreatech.koin.feature.lostandfound.ui.detail.LostAndFoundDetail -import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID +import `in`.koreatech.koin.core.navigation.Navigator +import `in`.koreatech.koin.feature.article.LostAndFoundReportActivity +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.article.detail.ArticleDetailFragment +import `in`.koreatech.koin.feature.article.ui.lostandfound.detail.LostAndFoundDetail +import javax.inject.Inject @AndroidEntryPoint class ArticleLostAndFoundDetailFragment : Fragment() { + + @Inject + lateinit var navigator: Navigator + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -44,16 +50,14 @@ class ArticleLostAndFoundDetailFragment : Fragment() { R.id.articleLostAndFoundDetailFragment_to_articleDetailFragment, Bundle().apply { putInt(ARTICLE_ID, hotArticleData.articleId) - putInt(NAVIGATED_BOARD_ID, hotArticleData.board.id) + putInt(ArticleDetailFragment.Companion.NAVIGATED_BOARD_ID, hotArticleData.board.id) } ) }, navigateToChatRoom = { articleId -> - Intent(requireContext(), ChatRoomActivity::class.java).apply { - putExtra(ARTICLE_ID, articleId) - }.also { - startActivity(it) - } + val intent = navigator.navigateToChatRoom(requireContext()) + intent.putExtra(ARTICLE_ID, articleId) + startActivity(intent) }, navigateToReport = { articleId -> Intent(requireContext(), LostAndFoundReportActivity::class.java).apply { diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteFoundFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteFoundFragment.kt similarity index 86% rename from koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteFoundFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteFoundFragment.kt index 89d15c53cc..5bdeeb225b 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteFoundFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteFoundFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.fragment import android.os.Bundle import android.view.LayoutInflater @@ -9,8 +9,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R -import `in`.koreatech.koin.feature.lostandfound.ui.write.LostAndFoundWriteArticle +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.LostAndFoundWriteArticle @AndroidEntryPoint class ArticleLostAndFoundWriteFoundFragment : Fragment() { diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteLostFragment.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteLostFragment.kt similarity index 86% rename from koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteLostFragment.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteLostFragment.kt index 19ce27e497..264a85d545 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/lostandfound/ArticleLostAndFoundWriteLostFragment.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/fragment/ArticleLostAndFoundWriteLostFragment.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.fragment import android.os.Bundle import android.view.LayoutInflater @@ -9,8 +9,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import `in`.koreatech.koin.R -import `in`.koreatech.koin.feature.lostandfound.ui.write.LostAndFoundWriteArticle +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.LostAndFoundWriteArticle @AndroidEntryPoint class ArticleLostAndFoundWriteLostFragment : Fragment() { diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFound.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFound.kt similarity index 94% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFound.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFound.kt index a607fa1f9d..b78681974a 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFound.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFound.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.list import android.widget.Toast import androidx.compose.animation.core.animateDpAsState @@ -37,16 +37,16 @@ import androidx.lifecycle.compose.LifecycleEventEffect import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.LoadingDialog -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundDialog -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundDropdownGroup -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundFAB -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundItem -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundKeywordGroup -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.LostAndFoundPagination -import `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component.lostAndFoundDialogStyle +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.LoadingDialog +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundDialog +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundDropdownGroup +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundFAB +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundItem +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundKeywordGroup +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.LostAndFoundPagination +import `in`.koreatech.koin.feature.article.ui.lostandfound.list.component.lostAndFoundDialogStyle import kotlinx.coroutines.launch import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundItemState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundItemState.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundItemState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundItemState.kt index ca4fa4f8af..64d540e8c7 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundItemState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundItemState.kt @@ -1,10 +1,10 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.list import android.os.Parcelable import `in`.koreatech.koin.domain.model.article.ArticleHeader import `in`.koreatech.koin.domain.model.article.ArticleLostAndFoundHeader -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import java.time.LocalDate import java.time.format.DateTimeFormatter import kotlinx.parcelize.Parcelize diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundSideEffect.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundSideEffect.kt similarity index 75% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundSideEffect.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundSideEffect.kt index ae929d33f2..43847cba76 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundSideEffect.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundSideEffect.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.list sealed class LostAndFoundSideEffect { data class PageChanged(val page: Int) : LostAndFoundSideEffect() diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundState.kt similarity index 83% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundState.kt index a3f95a1906..23f60cd1b6 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundState.kt @@ -1,7 +1,7 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound +package `in`.koreatech.koin.feature.article.ui.lostandfound.list import android.os.Parcelable -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import kotlinx.parcelize.Parcelize @Parcelize diff --git a/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundViewModel.kt new file mode 100644 index 0000000000..f1449defa6 --- /dev/null +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/LostAndFoundViewModel.kt @@ -0,0 +1,200 @@ +package `in`.koreatech.koin.feature.article.ui.lostandfound.list + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.model.user.User +import `in`.koreatech.koin.domain.usecase.article.FetchMyKeywordUseCase +import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchLostAndFoundArticlePaginationUseCase +import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchSearchedLostAndFoundArticlesUseCase +import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import javax.inject.Inject +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container +import timber.log.Timber + +@HiltViewModel +class LostAndFoundViewModel @Inject constructor( + private val fetchLostAndFoundArticlePaginationUseCase: FetchLostAndFoundArticlePaginationUseCase, + private val fetchSearchedLostAndFoundArticlesUseCase: FetchSearchedLostAndFoundArticlesUseCase, + private val fetchMyKeywordUseCase: FetchMyKeywordUseCase, + private val getUserStatusUseCase: GetUserStatusUseCase, + savedStateHandle: SavedStateHandle +) : ViewModel(), ContainerHost { + override val container = container( + initialState = LostAndFoundState(), + savedStateHandle = savedStateHandle + ) + + init { + fetchLostAndFoundList() + fetchMyKeyword() + getUserType() + } + + fun fetchLostAndFoundList() = viewModelScope.launch { + intent { + reduce { + state.copy( + isLoading = true + ) + } + + if (state.selectedKeyword.isEmpty()) { + fetchLostAndFoundArticlePaginationUseCase( + state.currentPage, + ARTICLES_PER_PAGE, + state.selectedType?.name + ).collectLatest { + reduce { + state.copy( + lostAndFoundList = it.articleLostAndFoundHeader.map { it.toLostAndFoundItemState() }, + currentCount = it.currentCount, + totalCount = it.totalCount, + currentPage = it.currentPage, + totalPage = it.totalPage, + isLoading = false + ) + } + } + } else { + fetchSearchedLostAndFoundArticlesUseCase( + state.selectedKeyword, + state.currentPage, + ARTICLES_PER_PAGE + ).collectLatest { + reduce { + state.copy( + lostAndFoundList = it.articleLostAndFoundHeader.map { it.toLostAndFoundItemState() }, + currentCount = it.currentCount, + totalCount = it.totalCount, + currentPage = it.currentPage, + totalPage = it.totalPage + ) + } + } + + reduce { + state.copy( + isLoading = false + ) + } + } + } + } + + fun fetchMyKeyword() = viewModelScope.launch { + fetchMyKeywordUseCase().catch { + intent { + reduce { + state.copy( + myKeywords = emptyList() + ) + } + Timber.d("Failed to fetch my keywords $it") + } + throw it + }.collectLatest { + intent { + reduce { + state.copy( + myKeywords = it + ) + } + postSideEffect(LostAndFoundSideEffect.KeywordUpdated) + } + } + } + + fun selectKeyword(it: String) { + intent { + reduce { + state.copy( + selectedKeyword = it + ) + } + } + } + + fun getUserType() = viewModelScope.launch { + getUserStatusUseCase().collectLatest { user -> + intent { + when (user) { + is User.Student -> reduce { + state.copy( + isAnonymous = false, + userType = user.userType + ) + } + + is User.General -> reduce { + state.copy( + isAnonymous = false, + userType = user.userType + ) + } + User.Anonymous -> reduce { + state.copy( + isAnonymous = true + ) + } + } + } + } + } + + fun changePage(page: Int) { + intent { + reduce { + state.copy( + currentPage = page + ) + } + postSideEffect(LostAndFoundSideEffect.PageChanged(page)) + } + } + + fun setShowLoginRequestDialog(showDialog: Boolean) = intent { + reduce { + state.copy( + showLoginRequestDialog = showDialog + ) + } + } + + fun setFabDialogExpanded(isExpanded: Boolean) = intent { + reduce { + state.copy( + isFabDialogExpanded = isExpanded + ) + } + } + + fun setDropdownExpanded(isExpanded: Boolean) = intent { + reduce { + state.copy( + isDropdownExpanded = isExpanded + ) + } + } + + fun setSelectedType(type: LostOrFoundType?) = intent { + reduce { + state.copy( + selectedType = type + ) + } + } + + companion object { + private const val ARTICLES_PER_PAGE = 10 + } +} diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDialog.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDialog.kt similarity index 98% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDialog.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDialog.kt index 63a62d7bbe..b5512a4c44 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDialog.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDialog.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -53,8 +53,7 @@ fun LostAndFoundDialog( lostAndFoundDialogStyle: LostAndFoundDialogStyle = lostAndFoundDialogStyle() ) { BasicAlertDialog( - modifier = - modifier + modifier = modifier .fillMaxWidth() .wrapContentHeight() .background( diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDropdownGroup.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDropdownGroup.kt similarity index 80% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDropdownGroup.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDropdownGroup.kt index bb1348b331..eb949d4484 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundDropdownGroup.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundDropdownGroup.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -8,9 +8,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.Dropdown -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.Dropdown +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType @Composable fun LostAndFoundDropdownGroup( @@ -26,14 +26,12 @@ fun LostAndFoundDropdownGroup( .padding(horizontal = 24.dp) ) { Dropdown( - title = - when (selectedType) { + title = when (selectedType) { LostOrFoundType.LOST -> stringResource(R.string.dropdown_item_lost) LostOrFoundType.FOUND -> stringResource(R.string.dropdown_item_found) else -> stringResource(R.string.dropdown_item_all) }, - items = - listOf( + items = listOf( stringResource(R.string.dropdown_item_all), stringResource(R.string.dropdown_item_found), stringResource(R.string.dropdown_item_lost) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundFAB.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundFAB.kt similarity index 93% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundFAB.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundFAB.kt index c13d7ed6ea..fb95970e51 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundFAB.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundFAB.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -28,10 +28,9 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme object LostAndFoundFABDefaults { val windowInsets: WindowInsets @Composable - get() = - WindowInsets.systemBars.only( - WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom - ) + get() = WindowInsets.systemBars.only( + WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom + ) } @Composable diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundItem.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundItem.kt similarity index 92% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundItem.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundItem.kt index 6e17463bd4..9b860f55e0 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundItem.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundItem.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -23,11 +23,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.LostItemTypeChip -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.util.getKoreanDayOfWeekShortName +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.LostItemTypeChip +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.util.getKoreanDayOfWeekShortName import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundKeywordGroup.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundKeywordGroup.kt similarity index 82% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundKeywordGroup.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundKeywordGroup.kt index 7e67aadcb3..3e98918ef1 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundKeywordGroup.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundKeywordGroup.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -30,9 +30,9 @@ import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.noRippleClickable -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.LostAndFoundTextChip -import `in`.koreatech.koin.feature.lostandfound.util.horizontalFadingEdge +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.LostAndFoundTextChip +import `in`.koreatech.koin.feature.article.util.horizontalFadingEdge @Composable fun LostAndFoundKeywordGroup( @@ -48,34 +48,29 @@ fun LostAndFoundKeywordGroup( * TextStyle for keyword chip * for match design with xml based view */ - val textStyle = - TextStyle( - fontSize = 14.sp, - fontStyle = FontStyle.Normal, - platformStyle = - PlatformTextStyle( - includeFontPadding = false - ), - lineHeightStyle = - LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None - ), - letterSpacing = 0.2.sp, - lineHeight = 20.sp - ) + val textStyle = TextStyle( + fontSize = 14.sp, + fontStyle = FontStyle.Normal, + platformStyle = PlatformTextStyle( + includeFontPadding = false + ), + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None + ), + letterSpacing = 0.2.sp, + lineHeight = 20.sp + ) Row( - modifier = - modifier + modifier = modifier .padding(vertical = 16.dp, horizontal = 24.dp) .horizontalFadingEdge(scrollState, 24.dp, Color.White) .horizontalScroll(scrollState), verticalAlignment = Alignment.CenterVertically ) { Box( - modifier = - Modifier + modifier = Modifier .size(32.dp) .background( color = Color(0xFFF5F5F5), @@ -101,8 +96,7 @@ fun LostAndFoundKeywordGroup( Spacer(modifier = Modifier.width(8.dp)) LostAndFoundTextChip( - modifier = - Modifier.defaultMinSize( + modifier = Modifier.defaultMinSize( minWidth = Dp.Unspecified, minHeight = 32.dp ), @@ -123,8 +117,7 @@ fun LostAndFoundKeywordGroup( keyWords.forEachIndexed { index, it -> LostAndFoundTextChip( - modifier = - Modifier.defaultMinSize( + modifier = Modifier.defaultMinSize( minWidth = Dp.Unspecified, minHeight = 32.dp ), @@ -140,8 +133,7 @@ fun LostAndFoundKeywordGroup( if (keyWords.isEmpty()) { LostAndFoundTextChip( - modifier = - Modifier.defaultMinSize( + modifier = Modifier.defaultMinSize( minWidth = Dp.Unspecified, minHeight = 32.dp ), diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundPagination.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundPagination.kt similarity index 94% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundPagination.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundPagination.kt index 08cad7f120..74ddc01bc3 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/component/LostAndFoundPagination.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/list/component/LostAndFoundPagination.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.list.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -18,7 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun LostAndFoundPagination( @@ -70,8 +70,7 @@ fun LostAndFoundPaginationButton( onClick: () -> Unit = {} ) { Text( - modifier = - modifier + modifier = modifier .noRippleClickable { if (isClickable) { onClick() @@ -91,8 +90,7 @@ fun LostAndFoundPaginationPageButton( onClick: (Int) -> Unit = {} ) { Box( - modifier = - modifier + modifier = modifier .size(32.dp) .noRippleClickable(onClick = { onClick(page) }) .clip(RoundedCornerShape(6.dp)) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReport.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReport.kt similarity index 88% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReport.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReport.kt index b572c1f167..177c3487e7 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReport.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReport.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report +package `in`.koreatech.koin.feature.article.ui.lostandfound.report import android.app.Activity import android.content.Context @@ -18,9 +18,9 @@ import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.component.topbar.KoinTopAppBar import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.ui.report.component.LostAndFoundReportContent -import `in`.koreatech.koin.feature.lostandfound.ui.report.component.lostAndFoundReportReasonList +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.lostandfound.report.component.LostAndFoundReportContent +import `in`.koreatech.koin.feature.article.ui.lostandfound.report.component.lostAndFoundReportReasonList import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect import timber.log.Timber @@ -46,8 +46,7 @@ fun LostAndFoundReport( KoinTopAppBar( title = stringResource(R.string.report_title), onNavigationIconClick = { (context as Activity).finish() }, - colors = - TopAppBarDefaults.centerAlignedTopAppBarColors().copy( + colors = TopAppBarDefaults.centerAlignedTopAppBarColors().copy( containerColor = KoinTheme.colors.primary500, navigationIconContentColor = KoinTheme.colors.neutral0, titleContentColor = KoinTheme.colors.neutral0, @@ -58,8 +57,7 @@ fun LostAndFoundReport( containerColor = KoinTheme.colors.neutral0 ) { contentPadding -> LostAndFoundReportContent( - modifier = - Modifier + modifier = Modifier .padding(contentPadding) .consumeWindowInsets(contentPadding), itemList = lostAndFoundReportReasonList, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportSideEffect.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportSideEffect.kt similarity index 74% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportSideEffect.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportSideEffect.kt index 1cd4ead4a5..c683a053d6 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportSideEffect.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportSideEffect.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report +package `in`.koreatech.koin.feature.article.ui.lostandfound.report sealed class LostAndFoundReportSideEffect { data object ReportSuccess : LostAndFoundReportSideEffect() diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportState.kt similarity index 66% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportState.kt index 1854186f8e..0e0093a0b7 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportState.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report +package `in`.koreatech.koin.feature.article.ui.lostandfound.report data class LostAndFoundReportState( val selectedReason: IntArray = intArrayOf(), diff --git a/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportViewModel.kt new file mode 100644 index 0000000000..3df2308a0b --- /dev/null +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/LostAndFoundReportViewModel.kt @@ -0,0 +1,80 @@ +package `in`.koreatech.koin.feature.article.ui.lostandfound.report + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.koin.domain.model.article.ArticleLostAndFoundReportItem +import `in`.koreatech.koin.domain.usecase.article.lostandfound.ReportLostAndFoundArticleUseCase +import `in`.koreatech.koin.feature.article.enums.ReportReason +import `in`.koreatech.koin.feature.article.ui.lostandfound.report.component.lostAndFoundReportReasonList +import javax.inject.Inject +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.blockingIntent +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container + +@HiltViewModel +class LostAndFoundReportViewModel @Inject constructor( + private val reportLostAndFoundArticleUseCase: ReportLostAndFoundArticleUseCase +) : ViewModel(), ContainerHost { + override val container = container(LostAndFoundReportState()) + + private fun addReportReason(reportReason: ReportReason) = intent { + reduce { + state.copy(selectedReason = state.selectedReason + lostAndFoundReportReasonList.indexOf(reportReason)) + } + } + + private fun removeReportReason(reportReason: ReportReason) = intent { + reduce { + state.copy( + selectedReason = state.selectedReason.filterNot { lostAndFoundReportReasonList[it] == reportReason }.toIntArray() + ) + } + } + + fun setReportReason(reportReason: ReportReason) = intent { + if (lostAndFoundReportReasonList.indexOf(reportReason) in state.selectedReason) { + removeReportReason(reportReason) + } else { + addReportReason(reportReason) + } + } + + fun setReportReasonDescription(reportReasonDescription: String) = blockingIntent { + reduce { + state.copy(reportReasonDescription = reportReasonDescription) + } + } + + fun reportArticle(articleId: Int) = viewModelScope.launch { + val reportReasonList = mutableListOf() + intent { + state.selectedReason.forEach { + if (lostAndFoundReportReasonList[it] == ReportReason.OTHER) { + reportReasonList.add( + ArticleLostAndFoundReportItem(lostAndFoundReportReasonList[it].title, state.reportReasonDescription) + ) + } else { + reportReasonList.add( + ArticleLostAndFoundReportItem( + lostAndFoundReportReasonList[it].title, + lostAndFoundReportReasonList[it].description + ) + ) + } + } + reportLostAndFoundArticleUseCase( + articleId, + reportReasonList + ).onSuccess { + postSideEffect(LostAndFoundReportSideEffect.ReportSuccess) + }.onFailure { + postSideEffect(LostAndFoundReportSideEffect.ReportFailure(it.message ?: "")) + } + } + } +} diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportContent.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportContent.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportContent.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportContent.kt index abc2647138..6eb04caaa9 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportContent.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportContent.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.report.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -17,8 +17,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.button.FilledButton import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.ReportReason +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.ReportReason @Composable fun LostAndFoundReportContent( @@ -33,8 +33,7 @@ fun LostAndFoundReportContent( val scrollState = rememberScrollState() Column( - modifier = - modifier + modifier = modifier .fillMaxSize() .imePadding() .verticalScroll(scrollState) @@ -54,8 +53,7 @@ fun LostAndFoundReportContent( FilledButton( text = stringResource(id = R.string.report_submit), onClick = onReport, - modifier = - Modifier + modifier = Modifier .padding(vertical = 20.dp, horizontal = 24.dp) .fillMaxWidth() ) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportHeader.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportHeader.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportHeader.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportHeader.kt index ac46743abe..cf41e66740 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportHeader.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportHeader.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.report.component import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -12,7 +12,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun LostAndFoundReportHeader(modifier: Modifier = Modifier) { diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportReasons.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportReasons.kt similarity index 93% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportReasons.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportReasons.kt index 0dcc8116c7..240e88a231 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/component/LostAndFoundReportReasons.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/report/component/LostAndFoundReportReasons.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.report.component import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -28,9 +28,9 @@ import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.REPORT_OTHER_REASON_MAX_LENGTH -import `in`.koreatech.koin.feature.lostandfound.enums.ReportReason +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.REPORT_OTHER_REASON_MAX_LENGTH +import `in`.koreatech.koin.feature.article.enums.ReportReason @Composable fun LostAndFoundReportReasons( @@ -56,8 +56,7 @@ fun LostAndFoundReportReasons( ) } else { LostAndFoundReportReasonItem( - modifier = - Modifier.noRippleClickable { + modifier = Modifier.noRippleClickable { onSelectedItemChange(index) }, reportReason = reportReason, @@ -113,8 +112,7 @@ fun LostAndFoundReportReasonOtherItem( isSelected: Boolean = false ) { Column( - modifier = - modifier + modifier = modifier .padding(vertical = 14.dp) .noRippleClickable { onFocused() @@ -125,8 +123,7 @@ fun LostAndFoundReportReasonOtherItem( ) { Image( modifier = Modifier.padding(horizontal = 8.dp), - painter = - painterResource( + painter = painterResource( id = if (isSelected) R.drawable.ic_report_item_selected else R.drawable.ic_report_item_unselected ), contentDescription = null @@ -184,8 +181,7 @@ fun ReportTextField( } BasicTextField( - modifier = - modifier + modifier = modifier .fillMaxWidth() .border(1.dp, color = KoinTheme.colors.neutral300, shape = KoinTheme.shapes.extraSmall) .padding(vertical = 12.dp, horizontal = 16.dp), diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticle.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticle.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticle.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticle.kt index 8c7dafbb3b..082900e9ea 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticle.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticle.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write +package `in`.koreatech.koin.feature.article.ui.lostandfound.write import android.content.Context import android.net.Uri @@ -36,18 +36,18 @@ import androidx.hilt.navigation.compose.hiltViewModel import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.MAX_ITEM_COUNT -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory.Companion.getCategoryKoreanWord -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleAddItemButton -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleDoneButton -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleHeader -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleItemChip -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleItemDetail -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleItemType -import `in`.koreatech.koin.feature.lostandfound.ui.write.component.WriteArticleUploadImage +import `in`.koreatech.koin.feature.article.MAX_ITEM_COUNT +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostItemCategory.Companion.getCategoryKoreanWord +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleAddItemButton +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleDoneButton +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleHeader +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleItemChip +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleItemDetail +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleItemType +import `in`.koreatech.koin.feature.article.ui.lostandfound.write.component.WriteArticleUploadImage import java.time.LocalDate import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -203,14 +203,13 @@ fun WriteFoundItemArticleImpl( onShowDatePickerChange: (showDatePicker: Boolean) -> Unit = {}, onDateChange: (date: LocalDate?) -> Unit = {} ) { - val pickMultipleMedia = - rememberLauncherForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(10)) { uris -> - if (uris.isNotEmpty()) { - uris.forEach { - onAddImageClick(it) - } + val pickMultipleMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(10)) { uris -> + if (uris.isNotEmpty()) { + uris.forEach { + onAddImageClick(it) } } + } val imageList = articleData.images @@ -292,9 +291,8 @@ fun handleSideEffect( if (fileNameIndex != -1 && fileSizeIndex != -1) { val fileName = cursor.getString(fileNameIndex) val fileSize = cursor.getLong(fileSizeIndex) - val fileType = - context.contentResolver.getType(imageContextUri) - ?: "image/${fileName.split(".").last()}" + val fileType = context.contentResolver.getType(imageContextUri) + ?: "image/${fileName.split(".").last()}" viewModel.getPreSignedUrl( fileSize, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleItemState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleItemState.kt similarity index 77% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleItemState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleItemState.kt index ca40bf1d55..11ba55ef6e 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleItemState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleItemState.kt @@ -1,10 +1,10 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write +package `in`.koreatech.koin.feature.article.ui.lostandfound.write import android.os.Parcelable import `in`.koreatech.koin.domain.model.article.ArticleLostAndFoundUpload -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory.Companion.getCategoryKoreanWord -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostItemCategory.Companion.getCategoryKoreanWord +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import java.time.LocalDate import kotlinx.parcelize.Parcelize diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleSideEffect.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleSideEffect.kt similarity index 95% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleSideEffect.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleSideEffect.kt index 40539d1fd2..ee55fd5c6d 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleSideEffect.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleSideEffect.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write +package `in`.koreatech.koin.feature.article.ui.lostandfound.write import android.net.Uri diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleState.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleState.kt similarity index 70% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleState.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleState.kt index 0c5acdeeba..3a9e5d9223 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleState.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleState.kt @@ -1,7 +1,7 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write +package `in`.koreatech.koin.feature.article.ui.lostandfound.write import android.os.Parcelable -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import kotlinx.parcelize.Parcelize @Parcelize diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleViewModel.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleViewModel.kt similarity index 71% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleViewModel.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleViewModel.kt index 95264e58c7..37c6a17722 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/LostAndFoundWriteArticleViewModel.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/LostAndFoundWriteArticleViewModel.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write +package `in`.koreatech.koin.feature.article.ui.lostandfound.write import android.net.Uri import androidx.lifecycle.SavedStateHandle @@ -8,9 +8,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.domain.usecase.article.lostandfound.UploadLostAndFoundArticleUseCase import `in`.koreatech.koin.domain.usecase.business.UploadFileUseCase import `in`.koreatech.koin.domain.usecase.presignedurl.GetLostAndFoundPreSignedUrlUseCase -import `in`.koreatech.koin.feature.lostandfound.IMAGE_MAX_COUNT -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.IMAGE_MAX_COUNT +import `in`.koreatech.koin.feature.article.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import java.time.LocalDate import javax.inject.Inject import kotlinx.coroutines.launch @@ -28,46 +28,41 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( private val uploadFilesUseCase: UploadFileUseCase ) : ViewModel(), ContainerHost { - override val container = - container( - LostAndFoundWriteArticleState(), - savedStateHandle - ) { - val rawLostOrFoundType = savedStateHandle.get(LOST_OR_FOUND_TYPE) - val lostOrFoundType = - LostOrFoundType.entries.find { - it.name == rawLostOrFoundType - } ?: LostOrFoundType.FOUND - setLostOrFoundType(lostOrFoundType) - addItem( - LostAndFoundWriteArticleItemState( - lostOrFoundType = lostOrFoundType - ) + override val container = container( + LostAndFoundWriteArticleState(), + savedStateHandle + ) { + val rawLostOrFoundType = savedStateHandle.get(LOST_OR_FOUND_TYPE) + val lostOrFoundType = LostOrFoundType.entries.find { + it.name == rawLostOrFoundType + } ?: LostOrFoundType.FOUND + setLostOrFoundType(lostOrFoundType) + addItem( + LostAndFoundWriteArticleItemState( + lostOrFoundType = lostOrFoundType ) - } + ) + } - private fun setLostOrFoundType(lostOrFoundType: LostOrFoundType) = - intent { - reduce { - state.copy(lostOrFoundType = lostOrFoundType) - } + private fun setLostOrFoundType(lostOrFoundType: LostOrFoundType) = intent { + reduce { + state.copy(lostOrFoundType = lostOrFoundType) } + } - fun addItem(item: LostAndFoundWriteArticleItemState) = - intent { - // postSideEffect(LostAndFoundWriteArticleSideEffect.AddItem(item)) - reduce { - state.copy(itemList = state.itemList + item) - } + fun addItem(item: LostAndFoundWriteArticleItemState) = intent { + // postSideEffect(LostAndFoundWriteArticleSideEffect.AddItem(item)) + reduce { + state.copy(itemList = state.itemList + item) } + } - fun removeItem(index: Int) = - intent { - // postSideEffect(LostAndFoundWriteArticleSideEffect.RemoveItem(index)) - reduce { - state.copy(itemList = state.itemList.filterIndexed { i, _ -> i != index }) - } + fun removeItem(index: Int) = intent { + // postSideEffect(LostAndFoundWriteArticleSideEffect.RemoveItem(index)) + reduce { + state.copy(itemList = state.itemList.filterIndexed { i, _ -> i != index }) } + } fun updateItemType( index: Int, @@ -77,8 +72,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( // postSideEffect(LostAndFoundWriteArticleSideEffect.UpdateItemType(index, category)) reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == index) { return@mapIndexed item.copy( category = category, @@ -111,12 +105,10 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( intent { reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy( - images = - item.images.mapIndexed { j, currentValue -> + images = item.images.mapIndexed { j, currentValue -> if (j == imageIndex) { return@mapIndexed fileUrl // Replace placeholder to real image url } else { @@ -179,8 +171,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy(images = item.images + "") // Add empty string as placeholder } else { @@ -206,8 +197,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( ) = intent { reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy(images = item.images.filterIndexed { j, _ -> j != imageIndex }) } else { @@ -225,8 +215,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( // postSideEffect(LostAndFoundWriteArticleSideEffect.UpdateDescription(itemIndex, content)) reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy(content = content) } else { @@ -244,8 +233,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( // postSideEffect(LostAndFoundWriteArticleSideEffect.UpdateLocation(itemIndex, foundPlace)) reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy( foundPlace = foundPlace, @@ -266,8 +254,7 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( // postSideEffect(LostAndFoundWriteArticleSideEffect.UpdateDate(itemIndex, date)) reduce { state.copy( - itemList = - state.itemList.mapIndexed { i, item -> + itemList = state.itemList.mapIndexed { i, item -> if (i == itemIndex) { return@mapIndexed item.copy(foundDate = date, dateRequired = date == null) } else { @@ -278,25 +265,23 @@ class LostAndFoundWriteArticleViewModel @Inject constructor( } } - fun checkAllFieldValid() = - intent { - postSideEffect(LostAndFoundWriteArticleSideEffect.CheckAllFieldValid(state.itemList)) - } + fun checkAllFieldValid() = intent { + postSideEffect(LostAndFoundWriteArticleSideEffect.CheckAllFieldValid(state.itemList)) + } - fun writeArticle() = - viewModelScope.launch { - intent { - uploadLostAndFoundArticleUseCase( - state.itemList.map { - it.toArticleLostAndFoundUpload() - } - ).onSuccess { - postSideEffect(LostAndFoundWriteArticleSideEffect.LostAndFoundWriteArticle(it.id)) - }.onFailure { - postSideEffect(LostAndFoundWriteArticleSideEffect.LostAndFoundWriteArticleFailed) + fun writeArticle() = viewModelScope.launch { + intent { + uploadLostAndFoundArticleUseCase( + state.itemList.map { + it.toArticleLostAndFoundUpload() } + ).onSuccess { + postSideEffect(LostAndFoundWriteArticleSideEffect.LostAndFoundWriteArticle(it.id)) + }.onFailure { + postSideEffect(LostAndFoundWriteArticleSideEffect.LostAndFoundWriteArticleFailed) } } + } companion object { const val LOST_OR_FOUND_TYPE = "lost_or_found_type" diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleAddItemButtom.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleAddItemButtom.kt similarity index 92% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleAddItemButtom.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleAddItemButtom.kt index 701a1ee7e7..78c7b0c6d0 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleAddItemButtom.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleAddItemButtom.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box @@ -21,7 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R @Composable fun WriteArticleAddItemButton( @@ -32,8 +32,7 @@ fun WriteArticleAddItemButton( Button( modifier = Modifier.align(Alignment.CenterEnd), onClick = onItemAdd, - colors = - ButtonDefaults.buttonColors( + colors = ButtonDefaults.buttonColors( containerColor = KoinTheme.colors.info200 ), shape = RoundedCornerShape(8.dp), diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleDoneButton.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleDoneButton.kt similarity index 89% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleDoneButton.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleDoneButton.kt index 5434d21ea9..df6a7df13e 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleDoneButton.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleDoneButton.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -22,15 +22,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R +import `in`.koreatech.koin.feature.article.R object WriteArticleDoneButtonDefaults { val windowInsets: WindowInsets @Composable - get() = - WindowInsets.systemBars.only( - WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom - ) + get() = WindowInsets.systemBars.only( + WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom + ) } @Composable diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleHeader.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleHeader.kt similarity index 83% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleHeader.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleHeader.kt index 845c70e011..e4c0ea4f77 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleHeader.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleHeader.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -13,8 +13,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType @Composable fun WriteArticleHeader( @@ -25,8 +25,7 @@ fun WriteArticleHeader( Row { Text( style = KoinTheme.typography.regular18, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(R.string.header_lost_title) LostOrFoundType.FOUND -> stringResource(R.string.header_found_title) } @@ -34,8 +33,7 @@ fun WriteArticleHeader( Spacer(modifier = Modifier.size(8.dp)) Image( modifier = Modifier.size(24.dp), - painter = - when (type) { + painter = when (type) { LostOrFoundType.LOST -> painterResource(R.drawable.ic_lost) LostOrFoundType.FOUND -> painterResource(R.drawable.ic_found) }, @@ -45,8 +43,7 @@ fun WriteArticleHeader( Text( color = KoinTheme.colors.neutral500, style = KoinTheme.typography.regular12, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(R.string.header_lost_description) LostOrFoundType.FOUND -> stringResource(R.string.header_found_description) } diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemChip.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemChip.kt similarity index 89% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemChip.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemChip.kt index 86c33f9e2c..aa2875debd 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemChip.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemChip.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -18,8 +18,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType @Composable fun WriteArticleItemChip( @@ -34,8 +34,7 @@ fun WriteArticleItemChip( verticalAlignment = Alignment.CenterVertically ) { Box( - modifier = - Modifier + modifier = Modifier .background( color = KoinTheme.colors.info200, shape = RoundedCornerShape(12.dp) @@ -58,8 +57,7 @@ fun WriteArticleItemChip( } if (shouldShowDelete) { Image( - modifier = - Modifier + modifier = Modifier .width(36.dp) .height(28.dp) .padding(vertical = 4.dp, horizontal = 8.dp) diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemDetail.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemDetail.kt similarity index 87% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemDetail.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemDetail.kt index 63596ce04d..8ac1b14243 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemDetail.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemDetail.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Image @@ -40,9 +40,9 @@ import `in`.koreatech.koin.core.designsystem.component.picker.rememberPickerStat import `in`.koreatech.koin.core.designsystem.component.text.LeadingIconText import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.DESCRIPTION_MAX_LENGTH -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.DESCRIPTION_MAX_LENGTH +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType import java.time.LocalDate @Composable @@ -82,8 +82,7 @@ fun WriteArticleItemDetail( } Column( - modifier = - modifier + modifier = modifier .fillMaxWidth() ) { Row( @@ -93,8 +92,7 @@ fun WriteArticleItemDetail( ) { Text( style = KoinTheme.typography.medium14, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_date) LostOrFoundType.FOUND -> stringResource(id = R.string.found_date) } @@ -103,8 +101,7 @@ fun WriteArticleItemDetail( if (dateRequired) { LeadingIconText( textStyle = KoinTheme.typography.medium12.copy(color = Color(0xFFF7941E)), - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_date_required) LostOrFoundType.FOUND -> stringResource(id = R.string.found_date_required) }, @@ -119,8 +116,7 @@ fun WriteArticleItemDetail( var dateComposablePosition by remember { mutableStateOf(Offset.Zero) } Box( - modifier = - Modifier + modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background(KoinTheme.colors.neutral100) .padding(vertical = 8.dp, horizontal = 16.dp) @@ -143,8 +139,7 @@ fun WriteArticleItemDetail( modifier = Modifier.weight(1f), color = KoinTheme.colors.neutral500, style = KoinTheme.typography.regular12, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_date_hint) LostOrFoundType.FOUND -> stringResource(id = R.string.found_date_hint) } @@ -165,13 +160,11 @@ fun WriteArticleItemDetail( ) Image( - modifier = - Modifier + modifier = Modifier .size(24.dp) .rotate(rotateDegree), painter = painterResource(id = R.drawable.ic_dropdown_arrow), - contentDescription = - when (type) { + contentDescription = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_date_hint) LostOrFoundType.FOUND -> stringResource(id = R.string.found_date_hint) } @@ -218,43 +211,38 @@ fun WriteArticleItemDetail( if (monthPickerState.selectedItem == now.monthValue.toString()) { dayList = (1..now.dayOfMonth).map { it.toString() } } else { - val lastDayOfMonth = - getLastDayOfMonth( - now.year, - Integer.parseInt(monthPickerState.selectedItem) - ) + val lastDayOfMonth = getLastDayOfMonth( + now.year, + Integer.parseInt(monthPickerState.selectedItem) + ) dayList = (1..lastDayOfMonth).map { it.toString() } } } else { monthList = (1..12).map { it.toString() } - val lastDayOfMonth = - getLastDayOfMonth( - Integer.parseInt(yearPickerState.selectedItem), - Integer.parseInt(monthPickerState.selectedItem) - ) + val lastDayOfMonth = getLastDayOfMonth( + Integer.parseInt(yearPickerState.selectedItem), + Integer.parseInt(monthPickerState.selectedItem) + ) dayList = (1..lastDayOfMonth).map { it.toString() } } } Popup( - offset = - IntOffset( + offset = IntOffset( dateComposablePosition.x.toInt(), dateComposablePosition.y.toInt() ) ) { Box( - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp, horizontal = 24.dp) .clip(RoundedCornerShape(8.dp)) .background(KoinTheme.colors.neutral100) ) { Row( - modifier = - Modifier + modifier = Modifier .padding(vertical = 12.dp, horizontal = 32.dp) .fillMaxWidth() ) { @@ -266,12 +254,10 @@ fun WriteArticleItemDetail( contentPadding = PaddingValues(vertical = 3.dp, horizontal = 12.dp), startIndex = yearPickerState.selectedItemIndex, infiniteScroll = false, - selectedTextStyle = - KoinTheme.typography.medium16.copy( + selectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ), - unselectedTextStyle = - KoinTheme.typography.medium16.copy( + unselectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ) ) @@ -283,12 +269,10 @@ fun WriteArticleItemDetail( contentPadding = PaddingValues(vertical = 3.dp, horizontal = 12.dp), startIndex = monthPickerState.selectedItemIndex, infiniteScroll = false, - selectedTextStyle = - KoinTheme.typography.medium16.copy( + selectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ), - unselectedTextStyle = - KoinTheme.typography.medium16.copy( + unselectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ) ) @@ -300,12 +284,10 @@ fun WriteArticleItemDetail( contentPadding = PaddingValues(vertical = 3.dp, horizontal = 12.dp), startIndex = dayPickerState.selectedItemIndex, infiniteScroll = false, - selectedTextStyle = - KoinTheme.typography.medium16.copy( + selectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ), - unselectedTextStyle = - KoinTheme.typography.medium16.copy( + unselectedTextStyle = KoinTheme.typography.medium16.copy( textAlign = TextAlign.Center ) ) @@ -324,8 +306,7 @@ fun WriteArticleItemDetail( ) { Text( style = KoinTheme.typography.medium14, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_location) LostOrFoundType.FOUND -> stringResource(id = R.string.found_location) } @@ -334,8 +315,7 @@ fun WriteArticleItemDetail( if (locationRequired) { LeadingIconText( textStyle = KoinTheme.typography.medium12.copy(color = Color(0xFFF7941E)), - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_location_required) LostOrFoundType.FOUND -> stringResource(id = R.string.found_location_required) }, @@ -351,13 +331,11 @@ fun WriteArticleItemDetail( value = location, onValueChange = onLocationChange, singleLine = true, - hint = - when (type) { + hint = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.lost_location_hint) LostOrFoundType.FOUND -> stringResource(id = R.string.found_location_hint) }, - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(8.dp)) .background(KoinTheme.colors.neutral100), @@ -394,8 +372,7 @@ fun WriteArticleItemDetail( } }, hint = stringResource(id = R.string.more_description_hint), - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(8.dp)) .background(KoinTheme.colors.neutral100), diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemType.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemType.kt similarity index 89% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemType.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemType.kt index 74d77d7c28..087032bfd3 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleItemType.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleItemType.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -17,9 +17,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.text.LeadingIconText import `in`.koreatech.koin.core.designsystem.theme.KoinTheme -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.component.ItemTypeChip -import `in`.koreatech.koin.feature.lostandfound.enums.LostItemCategory +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.component.ItemTypeChip +import `in`.koreatech.koin.feature.article.enums.LostItemCategory @Composable fun WriteArticleItemType( @@ -29,8 +29,7 @@ fun WriteArticleItemType( onItemSelected: (Int) -> Unit = {} ) { Column( - modifier = - modifier + modifier = modifier .fillMaxWidth() .padding(bottom = 24.dp) ) { diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleTextField.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleTextField.kt similarity index 92% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleTextField.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleTextField.kt index 550c78cdc1..e1686a96bd 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleTextField.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleTextField.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -31,8 +31,7 @@ fun WriteArticleTextField( } BasicTextField( - modifier = - Modifier + modifier = Modifier .padding(textPaddingValues) .fillMaxWidth(), value = value, diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleUploadImage.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleUploadImage.kt similarity index 90% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleUploadImage.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleUploadImage.kt index fe485c6c88..60608c129f 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/write/component/WriteArticleUploadImage.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/ui/lostandfound/write/component/WriteArticleUploadImage.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.write.component +package `in`.koreatech.koin.feature.article.ui.lostandfound.write.component import android.net.Uri import androidx.compose.foundation.Image @@ -45,9 +45,9 @@ import coil.request.ImageRequest import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.pxToDp -import `in`.koreatech.koin.feature.lostandfound.IMAGE_MAX_COUNT -import `in`.koreatech.koin.feature.lostandfound.R -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType +import `in`.koreatech.koin.feature.article.IMAGE_MAX_COUNT +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.enums.LostOrFoundType @Composable fun WriteArticleUploadImage( @@ -59,8 +59,7 @@ fun WriteArticleUploadImage( onRemoveImage: (index: Int) -> Unit = {} ) { Column( - modifier = - modifier + modifier = modifier .fillMaxWidth() .padding(bottom = 24.dp) ) { @@ -72,8 +71,7 @@ fun WriteArticleUploadImage( Text( modifier = Modifier.weight(1f), style = KoinTheme.typography.regular12, - text = - when (type) { + text = when (type) { LostOrFoundType.LOST -> stringResource(id = R.string.upload_image_of_lost_item) LostOrFoundType.FOUND -> stringResource(id = R.string.upload_image_of_found_item) }, @@ -89,8 +87,7 @@ fun WriteArticleUploadImage( if (uploadedImageCount > 0) { LazyRow( - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(8.dp)) .background(KoinTheme.colors.neutral100) @@ -113,8 +110,7 @@ fun WriteArticleUploadImage( Button( onClick = onUploadImage, - colors = - ButtonDefaults.buttonColors( + colors = ButtonDefaults.buttonColors( containerColor = KoinTheme.colors.info200 ), modifier = Modifier.fillMaxWidth(), @@ -157,8 +153,7 @@ fun WriteArticleUploadImageThumbnail( } } else { SubcomposeAsyncImage( - model = - ImageRequest.Builder(LocalContext.current) + model = ImageRequest.Builder(LocalContext.current) .data(imageUrl) .crossfade(true) .build(), @@ -172,8 +167,7 @@ fun WriteArticleUploadImageThumbnail( }, contentScale = ContentScale.Fit, contentDescription = null, - modifier = - modifier.onGloballyPositioned { + modifier = modifier.onGloballyPositioned { removeButtonPosition = it.positionInParent() + Offset( it.size.width.toFloat(), @@ -185,8 +179,7 @@ fun WriteArticleUploadImageThumbnail( Image( painter = painterResource(id = R.drawable.ic_delete_image), contentDescription = null, - modifier = - Modifier + modifier = Modifier .offset( x = removeButtonPosition.x.pxToDp - 8.dp, y = removeButtonPosition.y.pxToDp - 8.dp diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ContextExtensions.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ContextExtensions.kt similarity index 85% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ContextExtensions.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/ContextExtensions.kt index 1cf609ce85..1302bae463 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ContextExtensions.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ContextExtensions.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.util +package `in`.koreatech.koin.feature.article.util import android.content.Context import android.content.ContextWrapper diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/HtmlView.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/HtmlView.kt similarity index 80% rename from koin/src/main/java/in/koreatech/koin/ui/article/HtmlView.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/HtmlView.kt index 96af2650f3..a9a8f79414 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/HtmlView.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/HtmlView.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.util import android.annotation.SuppressLint import android.content.Context @@ -14,14 +14,13 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener -import `in`.koreatech.koin.R +import com.bumptech.glide.request.target.Target import `in`.koreatech.koin.core.dialog.ImageZoomableDialog import `in`.koreatech.koin.domain.model.article.html.CssAttribute import `in`.koreatech.koin.domain.model.article.html.HtmlAttribute import `in`.koreatech.koin.domain.model.article.html.HtmlTag -import `in`.koreatech.koin.ui.article.HtmlView.OnPostDrawListener -import `in`.koreatech.koin.ui.article.HtmlView.OnPreDrawListener -import `in`.koreatech.koin.ui.article.state.HtmlElement +import `in`.koreatech.koin.feature.article.R +import `in`.koreatech.koin.feature.article.ui.article.state.HtmlElement class HtmlView @JvmOverloads constructor( context: Context, @@ -60,18 +59,15 @@ class HtmlView @JvmOverloads constructor( if (lastAddedView is TextView && // 직전 View가 TextView이고 lastAddedView.textAlignment == self.styles[CssAttribute.TEXT_ALIGN].parseTextAlignment() ) { // Text-align이 같으면 TextView 재사용 - val frontLineBreak = - when (self.tag) { - HtmlTag.P, HtmlTag.DIV, HtmlTag.BR, HtmlTag.LI, HtmlTag.OL, HtmlTag.UL -> if (self.children.isEmpty()) "" else "\n" - else -> "" - } + val frontLineBreak = when (self.tag) { + HtmlTag.P, HtmlTag.DIV, HtmlTag.BR, HtmlTag.LI, HtmlTag.OL, HtmlTag.UL -> if (self.children.isEmpty()) "" else "\n" + else -> "" + } val listMarker = createListMarker(self.tag, html.tag, i) - val originalText = - SpannableStringBuilder((lastAddedView as TextView).text) - val newTextBuilder = - SpannableStringBuilder(frontLineBreak + listMarker + self.content) + val originalText = SpannableStringBuilder((lastAddedView as TextView).text) + val newTextBuilder = SpannableStringBuilder(frontLineBreak + listMarker + self.content) val newSpanned = newTextBuilder.getStyledText( 0, newTextBuilder.length, @@ -87,8 +83,7 @@ class HtmlView @JvmOverloads constructor( val listMarker = createListMarker(self.tag, html.tag, i) - val newTextBuilder = - SpannableStringBuilder(listMarker + self.content) + val newTextBuilder = SpannableStringBuilder(listMarker + self.content) text = newTextBuilder.getStyledText(0, newTextBuilder.length, self.styles) this.textAlignment = self.styles[CssAttribute.TEXT_ALIGN].parseTextAlignment() @@ -99,11 +94,10 @@ class HtmlView @JvmOverloads constructor( addHtmlView(self) } HtmlTag.HR -> { - val hr = - View(context).apply { - // layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 1) - // setBackgroundColor(Color.BLACK) - } + val hr = View(context).apply { + // layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 1) + // setBackgroundColor(Color.BLACK) + } addView(hr) lastAddedView = hr } @@ -129,10 +123,9 @@ class HtmlView @JvmOverloads constructor( } private fun drawImage(self: HtmlElement): ImageView { - val imageView = - ImageView(context).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) - } + val imageView = ImageView(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + } addView(imageView) Glide.with(context).load(self.attributes[HtmlAttribute.SRC]).error( Glide.with( @@ -145,7 +138,7 @@ class HtmlView @JvmOverloads constructor( override fun onLoadFailed( e: GlideException?, model: Any?, - target: com.bumptech.glide.request.target.Target, + target: Target, isFirstResource: Boolean ): Boolean { return false @@ -154,7 +147,7 @@ class HtmlView @JvmOverloads constructor( override fun onResourceReady( resource: Drawable, model: Any, - target: com.bumptech.glide.request.target.Target, + target: Target, dataSource: DataSource, isFirstResource: Boolean ): Boolean { diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/KoreanDateUtil.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/KoreanDateUtil.kt similarity index 91% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/KoreanDateUtil.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/KoreanDateUtil.kt index 53366109ff..b96de5a114 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/KoreanDateUtil.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/KoreanDateUtil.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.util +package `in`.koreatech.koin.feature.article.util import java.time.DayOfWeek import java.time.LocalDate diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ModifierUtil.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ModifierUtil.kt similarity index 83% rename from feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ModifierUtil.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/ModifierUtil.kt index ab35737da6..8dba604419 100644 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/util/ModifierUtil.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ModifierUtil.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.feature.lostandfound.util +package `in`.koreatech.koin.feature.article.util import androidx.compose.foundation.ScrollState import androidx.compose.ui.Modifier @@ -29,28 +29,23 @@ fun Modifier.horizontalFadingEdge( drawContent() drawRect( - brush = - Brush.horizontalGradient( - colors = - listOf( + brush = Brush.horizontalGradient( + colors = listOf( color, Color.Transparent ), startX = 0f, endX = startFadingEdgeStrength ), - size = - Size( + size = Size( startFadingEdgeStrength, this.size.height ) ) drawRect( - brush = - Brush.horizontalGradient( - colors = - listOf( + brush = Brush.horizontalGradient( + colors = listOf( Color.Transparent, color ), diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ParsingExtensions.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ParsingExtensions.kt similarity index 95% rename from koin/src/main/java/in/koreatech/koin/ui/article/ParsingExtensions.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/ParsingExtensions.kt index f47d88d3d9..8363fc77da 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/ParsingExtensions.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/ParsingExtensions.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.util import android.graphics.Color import `in`.koreatech.koin.core.util.RegexPatterns diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/TextExtensions.kt b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/TextExtensions.kt similarity index 98% rename from koin/src/main/java/in/koreatech/koin/ui/article/TextExtensions.kt rename to feature/article/src/main/java/in/koreatech/koin/feature/article/util/TextExtensions.kt index f1a92ad310..ffbb08b562 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/article/TextExtensions.kt +++ b/feature/article/src/main/java/in/koreatech/koin/feature/article/util/TextExtensions.kt @@ -1,4 +1,4 @@ -package `in`.koreatech.koin.ui.article +package `in`.koreatech.koin.feature.article.util import android.graphics.Color import android.graphics.Typeface diff --git a/feature/article/src/main/res/anim/fade.xml b/feature/article/src/main/res/anim/fade.xml new file mode 100644 index 0000000000..5436d31fc0 --- /dev/null +++ b/feature/article/src/main/res/anim/fade.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/feature/article/src/main/res/anim/fade_out.xml b/feature/article/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000000..ce741c752f --- /dev/null +++ b/feature/article/src/main/res/anim/fade_out.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/koin/src/main/res/anim/slide_in_from_left_in.xml b/feature/article/src/main/res/anim/slide_in_from_left_in.xml similarity index 100% rename from koin/src/main/res/anim/slide_in_from_left_in.xml rename to feature/article/src/main/res/anim/slide_in_from_left_in.xml diff --git a/koin/src/main/res/anim/slide_in_from_right_fade_in.xml b/feature/article/src/main/res/anim/slide_in_from_right_fade_in.xml similarity index 100% rename from koin/src/main/res/anim/slide_in_from_right_fade_in.xml rename to feature/article/src/main/res/anim/slide_in_from_right_fade_in.xml diff --git a/feature/lostandfound/src/main/res/drawable/background_chip_icon.xml b/feature/article/src/main/res/drawable/background_chip_icon.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/background_chip_icon.xml rename to feature/article/src/main/res/drawable/background_chip_icon.xml diff --git a/koin/src/main/res/drawable/ic_add_round.xml b/feature/article/src/main/res/drawable/ic_add_round.xml similarity index 100% rename from koin/src/main/res/drawable/ic_add_round.xml rename to feature/article/src/main/res/drawable/ic_add_round.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_article_delete.xml b/feature/article/src/main/res/drawable/ic_article_delete.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_article_delete.xml rename to feature/article/src/main/res/drawable/ic_article_delete.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_article_report.xml b/feature/article/src/main/res/drawable/ic_article_report.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_article_report.xml rename to feature/article/src/main/res/drawable/ic_article_report.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_article_reported.xml b/feature/article/src/main/res/drawable/ic_article_reported.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_article_reported.xml rename to feature/article/src/main/res/drawable/ic_article_reported.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_chat.xml b/feature/article/src/main/res/drawable/ic_chat.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_chat.xml rename to feature/article/src/main/res/drawable/ic_chat.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_delete_image.xml b/feature/article/src/main/res/drawable/ic_delete_image.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_delete_image.xml rename to feature/article/src/main/res/drawable/ic_delete_image.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_dropdown_arrow.xml b/feature/article/src/main/res/drawable/ic_dropdown_arrow.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_dropdown_arrow.xml rename to feature/article/src/main/res/drawable/ic_dropdown_arrow.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_fab_write.xml b/feature/article/src/main/res/drawable/ic_fab_write.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_fab_write.xml rename to feature/article/src/main/res/drawable/ic_fab_write.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_found.xml b/feature/article/src/main/res/drawable/ic_found.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_found.xml rename to feature/article/src/main/res/drawable/ic_found.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_go_to_keyword.xml b/feature/article/src/main/res/drawable/ic_go_to_keyword.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_go_to_keyword.xml rename to feature/article/src/main/res/drawable/ic_go_to_keyword.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_image_page_indicator_filled.xml b/feature/article/src/main/res/drawable/ic_image_page_indicator_filled.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_image_page_indicator_filled.xml rename to feature/article/src/main/res/drawable/ic_image_page_indicator_filled.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_image_page_indicator_not_filled.xml b/feature/article/src/main/res/drawable/ic_image_page_indicator_not_filled.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_image_page_indicator_not_filled.xml rename to feature/article/src/main/res/drawable/ic_image_page_indicator_not_filled.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_item_add.xml b/feature/article/src/main/res/drawable/ic_item_add.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_item_add.xml rename to feature/article/src/main/res/drawable/ic_item_add.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_item_delete.xml b/feature/article/src/main/res/drawable/ic_item_delete.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_item_delete.xml rename to feature/article/src/main/res/drawable/ic_item_delete.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_lost.xml b/feature/article/src/main/res/drawable/ic_lost.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_lost.xml rename to feature/article/src/main/res/drawable/ic_lost.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_report_item_selected.xml b/feature/article/src/main/res/drawable/ic_report_item_selected.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_report_item_selected.xml rename to feature/article/src/main/res/drawable/ic_report_item_selected.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_report_item_unselected.xml b/feature/article/src/main/res/drawable/ic_report_item_unselected.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_report_item_unselected.xml rename to feature/article/src/main/res/drawable/ic_report_item_unselected.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_required.xml b/feature/article/src/main/res/drawable/ic_required.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_required.xml rename to feature/article/src/main/res/drawable/ic_required.xml diff --git a/feature/lostandfound/src/main/res/drawable/ic_upload_image.xml b/feature/article/src/main/res/drawable/ic_upload_image.xml similarity index 100% rename from feature/lostandfound/src/main/res/drawable/ic_upload_image.xml rename to feature/article/src/main/res/drawable/ic_upload_image.xml diff --git a/koin/src/main/res/drawable/ic_view.xml b/feature/article/src/main/res/drawable/ic_view.xml similarity index 100% rename from koin/src/main/res/drawable/ic_view.xml rename to feature/article/src/main/res/drawable/ic_view.xml diff --git a/koin/src/main/res/drawable/tab_layout_underline.xml b/feature/article/src/main/res/drawable/tab_layout_underline.xml similarity index 100% rename from koin/src/main/res/drawable/tab_layout_underline.xml rename to feature/article/src/main/res/drawable/tab_layout_underline.xml diff --git a/koin/src/main/res/layout/activity_article.xml b/feature/article/src/main/res/layout/activity_article.xml similarity index 94% rename from koin/src/main/res/layout/activity_article.xml rename to feature/article/src/main/res/layout/activity_article.xml index 72baddf752..240d6557ca 100644 --- a/koin/src/main/res/layout/activity_article.xml +++ b/feature/article/src/main/res/layout/activity_article.xml @@ -7,7 +7,7 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.article.ArticleActivity"> + tools:context=".feature.article.ui.article.ArticleActivity"> - 게시글을 작성 하려면\n로그인이 필요해요. 로그인 후 분실물 주인을 찾아주세요! - 게시글이 존재하지 않습니다. - 글쓰기 주인을 찾아요 잃어버렸어요 @@ -122,4 +120,53 @@ 분실물 쪽지 보내기 습득물 쪽지 보내기 신고하기 + + 첨부파일 + 검색 + 다운로드가 완료되었습니다. + 원본 글 바로가기 + 아우누리 바로가기 + 학생종합경력개발 바로가기 + 다운로드 중 + 파일을 다운로드합니다. + 키워드 알림을 받으려면\n로그인이 필요해요. + 로그인 후 간편하게 공지사항 키워드\n알림을 받아보세요! + 게시판 + 공지사항 + 공지글 검색 + 키워드 관리 + 분실물 신고 + 습득물 신고 + 신고하기 + 이전 + 다음 + + 목록 + 다음 글 + 이전 글 + 인기있는 게시글 + 모두보기 + 내 키워드 + 키워드는 최대 10개까지 추가 가능합니다. + 추가 + 추천 키워드 + 키워드 알림 + 키워드가 포함된 게시물의 알림을 받을 수 있습니다. + 키워드 알림받기 + 알림받을 키워드를 입력해 주세요. + 키워드를 입력해주세요. + 키워드는 최대 10개까지 추가 가능합니다. + 이미 추가되어 있는 키워드입니다. + 키워드 길이는 2글자 이상, 20글자 이하입니다. + 키워드에 공백은 포함할 수 없습니다. + 새 키워드 추가 + 게시글이 존재하지 않습니다. + 많이 검색된 키워드 + 최근 검색기록 + 전체 삭제 + 검색어를 입력해주세요. + 일치하는 공지글이 없습니다.\n다른 키워드로 다시 시도해주세요. + + 알 수 없는 오류가 발생했습니다. + 설정에서 알림 권한을 허용해주세요 diff --git a/feature/lostandfound/src/main/AndroidManifest.xml b/feature/lostandfound/src/main/AndroidManifest.xml deleted file mode 100644 index a5918e68ab..0000000000 --- a/feature/lostandfound/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailViewModel.kt b/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailViewModel.kt deleted file mode 100644 index 1f7bbe49e8..0000000000 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/detail/LostAndFoundDetailViewModel.kt +++ /dev/null @@ -1,147 +0,0 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.detail - -import android.webkit.URLUtil -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import `in`.koreatech.koin.domain.model.user.User -import `in`.koreatech.koin.domain.usecase.article.lostandfound.DeleteArticleLostAndFoundUseCase -import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchHotArticlesUseCase -import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchLostAndFoundArticleUseCase -import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase -import `in`.koreatech.koin.feature.lostandfound.model.toArticleHeaderState -import javax.inject.Inject -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import org.orbitmvi.orbit.ContainerHost -import org.orbitmvi.orbit.syntax.simple.intent -import org.orbitmvi.orbit.syntax.simple.postSideEffect -import org.orbitmvi.orbit.syntax.simple.reduce -import org.orbitmvi.orbit.viewmodel.container -import retrofit2.HttpException - -@HiltViewModel -class LostAndFoundDetailViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - private val fetchLostAndFoundArticleUseCase: FetchLostAndFoundArticleUseCase, - private val fetchHotArticlesUseCase: FetchHotArticlesUseCase, - private val deleteArticleLostAndFoundUseCase: DeleteArticleLostAndFoundUseCase, - private val getUserStatusUseCase: GetUserStatusUseCase -) : ViewModel(), ContainerHost { - override val container = - container(LostAndFoundDetailState(), savedStateHandle) { - val articleId = savedStateHandle.get(ARTICLE_ID) - checkNotNull(articleId) - fetchLostAndFoundDetail(articleId) - } - - init { - initUserInfo() - fetchHotArticles() - } - - private fun initUserInfo() = - viewModelScope.launch { - getUserStatusUseCase().collectLatest { - intent { - when (it) { - is User.Student -> reduce { - state.copy( - isLoggedIn = true, - currentLoggedInUser = it.nickname ?: "" - ) - } - is User.General -> reduce { - state.copy( - isLoggedIn = true, - currentLoggedInUser = it.nickname ?: "" - ) - } - is User.Anonymous -> reduce { - state.copy(isLoggedIn = false) - } - } - } - } - } - - fun fetchLostAndFoundDetail(articleId: Int) = - viewModelScope.launch { - intent { - reduce { - state.copy( - isLoading = true - ) - } - fetchLostAndFoundArticleUseCase(articleId).catch { - if (it is HttpException && it.code() == 404) { - postSideEffect(LostAndFoundDetailSideEffect.DeletedArticle) - } - }.map { - it.toLostAndFoundDetailState() - }.collectLatest { article -> - reduce { - state.copy( - lostOrFound = article.lostOrFound, - id = article.id, - category = article.category, - foundPlace = article.foundPlace, - foundDate = article.foundDate, - content = article.content, - author = article.author, - images = article.images?.filter { URLUtil.isValidUrl(it.toString()) }, - registeredAt = article.registeredAt, - updatedAt = article.updatedAt, - isWriterCouncil = article.isWriterCouncil, - isMine = state.currentLoggedInUser == article.author, - isAuthorWithdraw = article.author == "탈퇴한 사용자", - isLoading = false - ) - } - } - } - } - - fun fetchHotArticles() = - viewModelScope.launch { - intent { - fetchHotArticlesUseCase().collectLatest { - reduce { - state.copy( - hotArticles = - it.filterIndexed { index, _ -> index < HOT_ARTICLE_COUNT } - .map { it.toArticleHeaderState() } - ) - } - } - } - } - - fun deleteArticle() = - viewModelScope.launch { - intent { - deleteArticleLostAndFoundUseCase(state.id).onSuccess { - postSideEffect(LostAndFoundDetailSideEffect.DeleteArticle(state.id)) - }.onFailure { - postSideEffect(LostAndFoundDetailSideEffect.DeleteArticleFailed) - } - } - } - - fun setShowDeleteDialog(show: Boolean) = - intent { - reduce { - state.copy( - showDeleteDialog = show - ) - } - } - - companion object { - const val HOT_ARTICLE_COUNT = 4 - const val ARTICLE_ID = "article_id" - } -} diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundViewModel.kt b/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundViewModel.kt deleted file mode 100644 index 0ba5ad84d5..0000000000 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/lostandfound/LostAndFoundViewModel.kt +++ /dev/null @@ -1,208 +0,0 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.lostandfound - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import `in`.koreatech.koin.domain.model.user.User -import `in`.koreatech.koin.domain.usecase.article.FetchMyKeywordUseCase -import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchLostAndFoundArticlePaginationUseCase -import `in`.koreatech.koin.domain.usecase.article.lostandfound.FetchSearchedLostAndFoundArticlesUseCase -import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase -import `in`.koreatech.koin.feature.lostandfound.enums.LostOrFoundType -import javax.inject.Inject -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import org.orbitmvi.orbit.ContainerHost -import org.orbitmvi.orbit.syntax.simple.intent -import org.orbitmvi.orbit.syntax.simple.postSideEffect -import org.orbitmvi.orbit.syntax.simple.reduce -import org.orbitmvi.orbit.viewmodel.container -import timber.log.Timber - -@HiltViewModel -class LostAndFoundViewModel @Inject constructor( - private val fetchLostAndFoundArticlePaginationUseCase: FetchLostAndFoundArticlePaginationUseCase, - private val fetchSearchedLostAndFoundArticlesUseCase: FetchSearchedLostAndFoundArticlesUseCase, - private val fetchMyKeywordUseCase: FetchMyKeywordUseCase, - private val getUserStatusUseCase: GetUserStatusUseCase, - savedStateHandle: SavedStateHandle -) : ViewModel(), ContainerHost { - override val container = - container( - initialState = LostAndFoundState(), - savedStateHandle = savedStateHandle - ) - - init { - fetchLostAndFoundList() - fetchMyKeyword() - getUserType() - } - - fun fetchLostAndFoundList() = - viewModelScope.launch { - intent { - reduce { - state.copy( - isLoading = true - ) - } - - if (state.selectedKeyword.isEmpty()) { - fetchLostAndFoundArticlePaginationUseCase( - state.currentPage, - ARTICLES_PER_PAGE, - state.selectedType?.name - ).collectLatest { - reduce { - state.copy( - lostAndFoundList = it.articleLostAndFoundHeader.map { it.toLostAndFoundItemState() }, - currentCount = it.currentCount, - totalCount = it.totalCount, - currentPage = it.currentPage, - totalPage = it.totalPage, - isLoading = false - ) - } - } - } else { - fetchSearchedLostAndFoundArticlesUseCase( - state.selectedKeyword, - state.currentPage, - ARTICLES_PER_PAGE - ).collectLatest { - reduce { - state.copy( - lostAndFoundList = it.articleLostAndFoundHeader.map { it.toLostAndFoundItemState() }, - currentCount = it.currentCount, - totalCount = it.totalCount, - currentPage = it.currentPage, - totalPage = it.totalPage - ) - } - } - - reduce { - state.copy( - isLoading = false - ) - } - } - } - } - - fun fetchMyKeyword() = - viewModelScope.launch { - fetchMyKeywordUseCase().catch { - intent { - reduce { - state.copy( - myKeywords = emptyList() - ) - } - Timber.d("Failed to fetch my keywords $it") - } - throw it - }.collectLatest { - intent { - reduce { - state.copy( - myKeywords = it - ) - } - postSideEffect(LostAndFoundSideEffect.KeywordUpdated) - } - } - } - - fun selectKeyword(it: String) { - intent { - reduce { - state.copy( - selectedKeyword = it - ) - } - } - } - - fun getUserType() = - viewModelScope.launch { - getUserStatusUseCase().collectLatest { user -> - intent { - when (user) { - is User.Student -> reduce { - state.copy( - isAnonymous = false, - userType = user.userType - ) - } - - is User.General -> reduce { - state.copy( - isAnonymous = false, - userType = user.userType - ) - } - User.Anonymous -> reduce { - state.copy( - isAnonymous = true - ) - } - } - } - } - } - - fun changePage(page: Int) { - intent { - reduce { - state.copy( - currentPage = page - ) - } - postSideEffect(LostAndFoundSideEffect.PageChanged(page)) - } - } - - fun setShowLoginRequestDialog(showDialog: Boolean) = - intent { - reduce { - state.copy( - showLoginRequestDialog = showDialog - ) - } - } - - fun setFabDialogExpanded(isExpanded: Boolean) = - intent { - reduce { - state.copy( - isFabDialogExpanded = isExpanded - ) - } - } - - fun setDropdownExpanded(isExpanded: Boolean) = - intent { - reduce { - state.copy( - isDropdownExpanded = isExpanded - ) - } - } - - fun setSelectedType(type: LostOrFoundType?) = - intent { - reduce { - state.copy( - selectedType = type - ) - } - } - - companion object { - private const val ARTICLES_PER_PAGE = 10 - } -} diff --git a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportViewModel.kt b/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportViewModel.kt deleted file mode 100644 index 16b9a7157e..0000000000 --- a/feature/lostandfound/src/main/java/in/koreatech/koin/feature/lostandfound/ui/report/LostAndFoundReportViewModel.kt +++ /dev/null @@ -1,86 +0,0 @@ -package `in`.koreatech.koin.feature.lostandfound.ui.report - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import `in`.koreatech.koin.domain.model.article.ArticleLostAndFoundReportItem -import `in`.koreatech.koin.domain.usecase.article.lostandfound.ReportLostAndFoundArticleUseCase -import `in`.koreatech.koin.feature.lostandfound.enums.ReportReason -import `in`.koreatech.koin.feature.lostandfound.ui.report.component.lostAndFoundReportReasonList -import javax.inject.Inject -import kotlinx.coroutines.launch -import org.orbitmvi.orbit.ContainerHost -import org.orbitmvi.orbit.syntax.simple.blockingIntent -import org.orbitmvi.orbit.syntax.simple.intent -import org.orbitmvi.orbit.syntax.simple.postSideEffect -import org.orbitmvi.orbit.syntax.simple.reduce -import org.orbitmvi.orbit.viewmodel.container - -@HiltViewModel -class LostAndFoundReportViewModel @Inject constructor( - private val reportLostAndFoundArticleUseCase: ReportLostAndFoundArticleUseCase -) : ViewModel(), ContainerHost { - override val container = - container(LostAndFoundReportState()) - - private fun addReportReason(reportReason: ReportReason) = - intent { - reduce { - state.copy(selectedReason = state.selectedReason + lostAndFoundReportReasonList.indexOf(reportReason)) - } - } - - private fun removeReportReason(reportReason: ReportReason) = - intent { - reduce { - state.copy( - selectedReason = state.selectedReason.filterNot { lostAndFoundReportReasonList[it] == reportReason }.toIntArray() - ) - } - } - - fun setReportReason(reportReason: ReportReason) = - intent { - if (lostAndFoundReportReasonList.indexOf(reportReason) in state.selectedReason) { - removeReportReason(reportReason) - } else { - addReportReason(reportReason) - } - } - - fun setReportReasonDescription(reportReasonDescription: String) = - blockingIntent { - reduce { - state.copy(reportReasonDescription = reportReasonDescription) - } - } - - fun reportArticle(articleId: Int) = - viewModelScope.launch { - val reportReasonList = mutableListOf() - intent { - state.selectedReason.forEach { - if (lostAndFoundReportReasonList[it] == ReportReason.OTHER) { - reportReasonList.add( - ArticleLostAndFoundReportItem(lostAndFoundReportReasonList[it].title, state.reportReasonDescription) - ) - } else { - reportReasonList.add( - ArticleLostAndFoundReportItem( - lostAndFoundReportReasonList[it].title, - lostAndFoundReportReasonList[it].description - ) - ) - } - } - reportLostAndFoundArticleUseCase( - articleId, - reportReasonList - ).onSuccess { - postSideEffect(LostAndFoundReportSideEffect.ReportSuccess) - }.onFailure { - postSideEffect(LostAndFoundReportSideEffect.ReportFailure(it.message ?: "")) - } - } - } -} diff --git a/koin/build.gradle.kts b/koin/build.gradle.kts index 3b28be707b..08890dc746 100644 --- a/koin/build.gradle.kts +++ b/koin/build.gradle.kts @@ -106,7 +106,7 @@ dependencies { implementation(projects.core.onboarding) implementation(projects.feature.timetable) implementation(projects.feature.bus) - implementation(projects.feature.lostandfound) + implementation(projects.feature.article) implementation(projects.feature.chat) implementation(projects.feature.banner) implementation(projects.feature.store) diff --git a/koin/src/main/AndroidManifest.xml b/koin/src/main/AndroidManifest.xml index aa81965b74..fcbb5f4b07 100644 --- a/koin/src/main/AndroidManifest.xml +++ b/koin/src/main/AndroidManifest.xml @@ -86,28 +86,6 @@ - - - - - - - - - - - - ALL - 14 -> LOSTANDFOUND - 5 -> NORMAL - 6 -> SCHOLARSHIP - 7 -> SCHOOL - 8 -> RECRUIT - 12 -> IPP - 13 -> STUDENT - 9 -> KOIN - else -> ALL - } - } - } -} - -/** - * Koreatech 페이지로 이동할 때 사용 - * @property NONE 링크 없음 - * @property ARTICLE 원본 게시글로 이동 (로그인 필요없는 게시판) - * @property PORTAL 아우누리로 이동 (로그인 필요한 게시판) - * @property STEMS 학생종합경력개발로 이동 (로그인 필요한 게시판) - */ -enum class LinkType { - NONE, - ARTICLE, - PORTAL, - STEMS -} diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleHeaderState.kt b/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleHeaderState.kt deleted file mode 100644 index dc7338073c..0000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/article/state/ArticleHeaderState.kt +++ /dev/null @@ -1,27 +0,0 @@ -package `in`.koreatech.koin.ui.article.state - -import android.os.Parcelable -import `in`.koreatech.koin.domain.model.article.ArticleHeader -import `in`.koreatech.koin.ui.article.ArticleBoardType -import kotlinx.parcelize.Parcelize - -@Parcelize -data class ArticleHeaderState( - val id: Int, - val board: ArticleBoardType, - val title: String, - val author: String, - val viewCount: Int, - val registeredAt: String, - val updatedAt: String -) : Parcelable - -fun ArticleHeader.toArticleHeaderState() = ArticleHeaderState( - id = id, - board = ArticleBoardType.entries.firstOrNull { it.id == boardId } ?: ArticleBoardType.ALL, - title = title, - author = author, - viewCount = viewCount, - registeredAt = registeredAt, - updatedAt = updatedAt -) diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleDetailViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleDetailViewModel.kt deleted file mode 100644 index 10f34bd044..0000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleDetailViewModel.kt +++ /dev/null @@ -1,102 +0,0 @@ -package `in`.koreatech.koin.ui.article.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import `in`.koreatech.koin.core.viewmodel.BaseViewModel -import `in`.koreatech.koin.domain.repository.ArticleRepository -import `in`.koreatech.koin.ui.article.ArticleBoardType -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState -import `in`.koreatech.koin.ui.article.state.ArticleState -import `in`.koreatech.koin.ui.article.state.toArticleHeaderState -import `in`.koreatech.koin.ui.article.state.toArticleState -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn - -class ArticleDetailViewModel @AssistedInject constructor( - @Assisted("articleId") articleId: Int, - @Assisted("navigatedBoardId") val navigatedBoardId: Int, - private val articleRepository: ArticleRepository -) : BaseViewModel() { - val article: StateFlow = - articleRepository.fetchArticle(articleId, navigatedBoardId) - .onStart { - _isLoading.value = true - }.map { - it.toArticleState() - }.onEach { - _isLoading.value = false - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = - ArticleState( - header = - ArticleHeaderState( - id = 0, - board = ArticleBoardType.ALL, - title = "", - author = "", - viewCount = 0, - registeredAt = "", - updatedAt = "" - ), - content = "", - prevArticleId = null, - nextArticleId = null, - attachments = listOf(), - url = "" - ) - ) - - val hotArticles: StateFlow> = - articleRepository.fetchHotArticleHeaders() - .map { - var doesHotContainsThis = false - it.filterIndexed { index, hotArticleHeader -> - if (articleId == hotArticleHeader.id) { - doesHotContainsThis = true - } - articleId != hotArticleHeader.id && index < (HOT_ARTICLE_COUNT + if (doesHotContainsThis) 1 else 0) - }.map { it.toArticleHeaderState() } - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = listOf() - ) - - fun setIsLoading(isLoading: Boolean) { - _isLoading.value = isLoading - } - - @AssistedFactory - interface Factory { - fun create( - @Assisted("articleId") articleId: Int, - @Assisted("navigatedBoardId") navigatedBoardId: Int - ): ArticleDetailViewModel - } - - companion object { - const val HOT_ARTICLE_COUNT = 4 - - fun provideFactory( - assistedFactory: Factory, - article: Int, - boardId: Int - ): ViewModelProvider.Factory { - return object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return assistedFactory.create(article, boardId) as T - } - } - } - } -} diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleKeywordViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleKeywordViewModel.kt deleted file mode 100644 index 91b7493a17..0000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/article/viewmodel/ArticleKeywordViewModel.kt +++ /dev/null @@ -1,152 +0,0 @@ -package `in`.koreatech.koin.ui.article.viewmodel - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import `in`.koreatech.koin.core.viewmodel.BaseViewModel -import `in`.koreatech.koin.domain.model.user.User -import `in`.koreatech.koin.domain.repository.ArticleRepository -import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase -import javax.inject.Inject -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.flow.stateIn - -@HiltViewModel -class ArticleKeywordViewModel @Inject constructor( - private val savedStateHandle: SavedStateHandle, - private val articleRepository: ArticleRepository, - getUserStatusUseCase: GetUserStatusUseCase -) : BaseViewModel() { - val user: StateFlow = - getUserStatusUseCase() - .stateIn(viewModelScope, SharingStarted.Eagerly, User.Anonymous) - - val keywordInputUiState: StateFlow = - savedStateHandle.getStateFlow(KEYWORD_INPUT, "").map { - if (it.isEmpty()) KeywordInputUiState.Empty else KeywordInputUiState.Valid(it) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = KeywordInputUiState.Empty - ) - - val myKeywords: StateFlow> = - articleRepository.fetchMyKeyword() - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = listOf() - ) - - private val _keywordAddUiState = - MutableSharedFlow( - extraBufferCapacity = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - val keywordAddUiState: SharedFlow = - _keywordAddUiState - .shareIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000) - ) - - val suggestedKeywords: StateFlow> = - articleRepository.fetchKeywordSuggestions() - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = listOf() - ) - - fun addKeyword(keyword: String) { - val trimmedKeyword = - keyword.trim().ifEmpty { - _keywordAddUiState.tryEmit(KeywordAddUiState.RequireInput) - return - } - if (myKeywords.value.size >= MAX_KEYWORD_COUNT) { - _keywordAddUiState.tryEmit(KeywordAddUiState.LimitExceeded) - return - } - - if (trimmedKeyword.length > MAX_KEYWORD_LENGTH || trimmedKeyword.length < MIN_KEYWORD_LENGTH) { - _keywordAddUiState.tryEmit(KeywordAddUiState.InvalidLength) - return - } - - if (myKeywords.value.contains(trimmedKeyword)) { - _keywordAddUiState.tryEmit(KeywordAddUiState.AlreadyExist) - return - } - - if (trimmedKeywordRegex.containsMatchIn(trimmedKeyword)) { // jusang-regex-opt - _keywordAddUiState.tryEmit(KeywordAddUiState.BlankNotAllowed) - return - } - - articleRepository.saveKeyword(trimmedKeyword).onStart { - _keywordAddUiState.emit(KeywordAddUiState.Loading) - }.onEach { - _keywordAddUiState.emit(KeywordAddUiState.Success(trimmedKeyword)) - }.catch { - _keywordAddUiState.emit(KeywordAddUiState.Error) - }.launchIn(viewModelScope) - } - - fun deleteKeyword(keyword: String) { - articleRepository.deleteKeyword(keyword).onEach { - _keywordAddUiState.emit(KeywordAddUiState.Success(keyword)) - }.catch { - _keywordAddUiState.emit(KeywordAddUiState.Error) - }.launchIn(viewModelScope) - } - - fun onKeywordInputChanged(keyword: String) { - savedStateHandle[KEYWORD_INPUT] = keyword - } - - companion object { - const val MAX_KEYWORD_COUNT = 10 - const val MAX_SUGGEST_KEYWORD_COUNT = 5 - const val MAX_KEYWORD_LENGTH = 20 - const val MIN_KEYWORD_LENGTH = 2 - const val KEYWORD_INPUT = "keyword_input" - val trimmedKeywordRegex = Regex("""\s+""") - } -} - -sealed interface KeywordAddUiState { - data object Nothing : KeywordAddUiState - - data object Loading : KeywordAddUiState - - data class Success(val keyword: String) : KeywordAddUiState - - data object AlreadyExist : KeywordAddUiState - - data object LimitExceeded : KeywordAddUiState - - data object BlankNotAllowed : KeywordAddUiState - - data object InvalidLength : KeywordAddUiState - - data object RequireInput : KeywordAddUiState - - data object Error : KeywordAddUiState -} - -sealed interface KeywordInputUiState { - data object Empty : KeywordInputUiState - - data class Valid(val keyword: String) : KeywordInputUiState -} diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 7ea0330473..3a9671bf1c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -54,12 +54,12 @@ import `in`.koreatech.koin.core.util.blueStatusBar import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityMainBinding import `in`.koreatech.koin.domain.model.article.ArticleNotiType +import `in`.koreatech.koin.feature.article.ArticleActivity import `in`.koreatech.koin.feature.banner.ui.BannerActivity import `in`.koreatech.koin.feature.club.ui.MainClubWidgetA import `in`.koreatech.koin.feature.club.ui.MainClubWidgetB import `in`.koreatech.koin.feature.store.MainStoreWidget import `in`.koreatech.koin.navigation.SchemeType -import `in`.koreatech.koin.ui.article.ArticleActivity import `in`.koreatech.koin.ui.main.adapter.StoreCategoriesRecyclerAdapter import `in`.koreatech.koin.ui.main.compose.HotArticlePager import `in`.koreatech.koin.ui.main.viewmodel.MainActivityViewModel diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 7b63468bcc..8a676a2ab6 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -41,13 +41,13 @@ import `in`.koreatech.koin.core.util.blueStatusBar import `in`.koreatech.koin.core.util.whiteStatusBar import `in`.koreatech.koin.data.constant.URLConstant import `in`.koreatech.koin.domain.model.user.User +import `in`.koreatech.koin.feature.article.ArticleActivity import `in`.koreatech.koin.feature.chat.ui.list.ChatListActivity import `in`.koreatech.koin.feature.club.ui.ClubActivity import `in`.koreatech.koin.feature.dining.ui.DiningActivity import `in`.koreatech.koin.feature.store.StoreActivity import `in`.koreatech.koin.feature.user.ui.signin.SignInActivity import `in`.koreatech.koin.feature.user.ui.signup.SignUpActivity -import `in`.koreatech.koin.ui.article.ArticleActivity import `in`.koreatech.koin.ui.land.LandActivity import `in`.koreatech.koin.ui.main.activity.MainActivity import `in`.koreatech.koin.ui.navigation.state.MenuState diff --git a/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt index ad9e6fd63e..55c002dbfc 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/splash/SplashActivity.kt @@ -35,7 +35,7 @@ import `in`.koreatech.koin.core.navigation.utils.EXTRA_TYPE import `in`.koreatech.koin.core.onboarding.OnboardingManager import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.domain.state.version.VersionUpdatePriority -import `in`.koreatech.koin.ui.article.ArticleActivity +import `in`.koreatech.koin.feature.article.ArticleActivity import `in`.koreatech.koin.ui.forceupdate.ForceUpdateActivity import `in`.koreatech.koin.ui.main.activity.MainActivity import `in`.koreatech.koin.ui.splash.state.TokenState diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml index 39548b086a..e3fc633639 100644 --- a/koin/src/main/res/values/strings.xml +++ b/koin/src/main/res/values/strings.xml @@ -232,7 +232,6 @@ %d/%d 메뉴 더보기 메뉴 접기 - 검색 일반 @@ -521,77 +520,12 @@ 알림 설정 바로가기 - 게시판 - 공지사항 - 공지글 검색 - 키워드 관리 - 분실물 신고 - 습득물 신고 - 더보기 - 자유게시판 - 취업게시판 - 익명게시판 - 전체공지 - 분실물 - 일반공지 - 일반 - 학사공지 - 학사 - 장학공지 - 장학 - 취업공지 - 취업 - 코인 - 질문게시판 - 홍보게시판 - 현장실습 - 현장실습 - 학생생활 - 학생 - 신고하기 - 이전 - 다음 - - 목록 - 다음 글 - 이전 글 - 원본 글 바로가기 - 아우누리 바로가기 - 학생종합경력개발 바로가기 - 인기있는 게시글 - 모두보기 - 내 키워드 - 키워드는 최대 10개까지 추가 가능합니다. - 추가 - 추천 키워드 - 키워드 알림 - 키워드가 포함된 게시물의 알림을 받을 수 있습니다. - 키워드 알림받기 - 알림받을 키워드를 입력해 주세요. - 키워드를 입력해주세요. - 키워드는 최대 10개까지 추가 가능합니다. - 이미 추가되어 있는 키워드입니다. - 키워드 길이는 2글자 이상, 20글자 이하입니다. - 키워드에 공백은 포함할 수 없습니다. - 새 키워드 추가 - 게시글이 존재하지 않습니다. - 많이 검색된 키워드 - 최근 검색기록 - 전체 삭제 - 키워드 알림을 받으려면\n로그인이 필요해요. - 로그인 후 간편하게 공지사항 키워드\n알림을 받아보세요! - 검색어를 입력해주세요. - 일치하는 공지글이 없습니다.\n다른 키워드로 다시 시도해주세요. 지금 인기있는 공지 자취방 양도글, 가장 먼저 확인하고 싶을 때? 공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기 분실물은 여기 다 모아뒀어요 바로가기 >> 첨부파일 - 다운로드 중 - 파일을 다운로드합니다. - 다운로드가 완료되었습니다. - 리뷰 (%1$s) diff --git a/settings.gradle b/settings.gradle index 4c55aa0100..d520fcaed4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,7 +30,7 @@ include ':feature:timetable' include ':core:designsystem' include ':feature:bus' include ':core:analytics' -include ':feature:lostandfound' +include ':feature:article' include ':feature:chat' include ':feature:banner' include ':feature:store'