diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f9e4a639..884dc307 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.google.firebase.crashlytics) implementation(libs.firebase.analytics) + implementation(libs.firebase.config) implementation(libs.accompanist.permissions) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c6e63c6..3e3d7b32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,12 +49,14 @@ kotlinxSerializationJson = "1.5.1" # Credentials credentials = "1.3.0-alpha01" googleid = "1.1.1" +firebaseConfigKtx = "22.0.1" [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } firebase-analytics = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } +firebase-config = { module = "com.google.firebase:firebase-config" } google-firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } @@ -116,6 +118,7 @@ androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation # firebase firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version.ref = "firebaseCrashlytics" } firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx", version.ref = "firebaseFirestoreKtx" } +firebase-config-ktx = { group = "com.google.firebase", name = "firebase-config-ktx", version.ref = "firebaseConfigKtx" } # datastore datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } diff --git a/main-data/build.gradle.kts b/main-data/build.gradle.kts index c224ae9c..9c07855a 100644 --- a/main-data/build.gradle.kts +++ b/main-data/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("hongikyeolgong2.android.library") + alias(libs.plugins.kotlin.serialization) } android { @@ -11,4 +12,7 @@ dependencies { implementation(projects.core.remote) implementation(libs.firebase.firestore.ktx) + implementation(libs.firebase.config.ktx) + implementation(libs.datastore.preferences) + implementation(libs.kotlinx.serialization.json) } diff --git a/main-data/src/main/java/com/teamhy2/main/data/datasource/RemoteConfigPromotionDataSource.kt b/main-data/src/main/java/com/teamhy2/main/data/datasource/RemoteConfigPromotionDataSource.kt new file mode 100644 index 00000000..3efc153c --- /dev/null +++ b/main-data/src/main/java/com/teamhy2/main/data/datasource/RemoteConfigPromotionDataSource.kt @@ -0,0 +1,64 @@ +package com.teamhy2.main.data.datasource + +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.teamhy2.main.data.dto.PromotionDto +import com.teamhy2.main.data.mapper.toDomain +import com.teamhy2.main.domain.datasource.PromotionDataSource +import com.teamhy2.main.domain.model.Promotion +import com.teamhy2.main.domain.util.DateUtil +import kotlinx.coroutines.tasks.await +import kotlinx.serialization.json.Json +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +class RemoteConfigPromotionDataSource + @Inject + constructor() : PromotionDataSource { + private val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig + + init { + remoteConfig.setDefaultsAsync(DEFAULTS) + } + + override suspend fun fetchPromotionData(): Result { + return runCatching { + remoteConfig.fetchAndActivate().await() + val promotionDataJson: String = remoteConfig.getString(PROMOTION_POPUP_KEY) + val promotionDto: PromotionDto = Json.decodeFromString(promotionDataJson) + promotionDto.copy( + isActive = + calculatePromotionActive( + promotionDto.startDate, + promotionDto.endDate, + ), + ).toDomain() + } + } + + private fun calculatePromotionActive( + startDate: String, + endDate: String, + ): Boolean { + return runCatching { + val formatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE + val startDate: LocalDate = LocalDate.parse(startDate, formatter) + val endDate: LocalDate = LocalDate.parse(endDate, formatter) + + DateUtil.isTodayWithinDateRange(startDate = startDate, endDate = endDate) + }.getOrDefault(false) + } + + companion object { + const val PROMOTION_POPUP_KEY = "promotionPopup" + const val DEFAULT_PROMOTION_JSON = + """{"imageUrl":"","detailUrl":"","startDate":"","endDate":""}""" + + val DEFAULTS = + mapOf( + PROMOTION_POPUP_KEY to DEFAULT_PROMOTION_JSON, + ) + } + } diff --git a/main-data/src/main/java/com/teamhy2/main/data/datastore/PromotionDataStore.kt b/main-data/src/main/java/com/teamhy2/main/data/datastore/PromotionDataStore.kt new file mode 100644 index 00000000..be721ef7 --- /dev/null +++ b/main-data/src/main/java/com/teamhy2/main/data/datastore/PromotionDataStore.kt @@ -0,0 +1,57 @@ +package com.teamhy2.main.data.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.MutablePreferences +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.teamhy2.main.domain.util.DateUtil +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +class PromotionDataStore + @Inject + constructor( + private val dataStore: DataStore, + ) { + private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE + + val isPromotionDismissed: Flow = + dataStore.data.map { preferences -> + val startDateString: String? = preferences[START_DATE] + val endDateString: String? = preferences[END_DATE] + requireNotNull(startDateString) { ERROR_START_DATE_MISSING } + requireNotNull(endDateString) { ERROR_END_DATE_MISSING } + + val startDate: LocalDate = LocalDate.parse(startDateString, formatter) + val endDate: LocalDate = LocalDate.parse(endDateString, formatter) + + DateUtil.isTodayWithinDateRange(startDate = startDate, endDate = endDate) + } + + suspend fun savePromotionDismissPeriod( + startDate: LocalDate, + endDate: LocalDate, + ) { + dataStore.edit { preferences: MutablePreferences -> + preferences[START_DATE] = startDate.format(formatter) + preferences[END_DATE] = endDate.format(formatter) + } + } + + companion object Keys { + private const val PROMOTION_START_DATE_KEY = "promotion_start_date" + private const val PROMOTION_END_DATE_KEY = "promotion_end_date" + + const val ERROR_START_DATE_MISSING = "DataStore에 시작 날짜가 없습니다." + const val ERROR_END_DATE_MISSING = "DataStore에 종료 날짜가 없습니다." + + val START_DATE: Preferences.Key = + stringPreferencesKey(PROMOTION_START_DATE_KEY) + val END_DATE: Preferences.Key = + stringPreferencesKey(PROMOTION_END_DATE_KEY) + } + } diff --git a/main-data/src/main/java/com/teamhy2/main/data/di/MainModule.kt b/main-data/src/main/java/com/teamhy2/main/data/di/MainModule.kt index 66d132a7..70db3838 100644 --- a/main-data/src/main/java/com/teamhy2/main/data/di/MainModule.kt +++ b/main-data/src/main/java/com/teamhy2/main/data/di/MainModule.kt @@ -1,7 +1,11 @@ package com.teamhy2.main.data.di +import com.teamhy2.main.data.datasource.RemoteConfigPromotionDataSource +import com.teamhy2.main.data.repository.DefaultPromotionRepository import com.teamhy2.main.data.repository.RemoteStudyDayRepository import com.teamhy2.main.data.repository.RemoteWiseSayingRepository +import com.teamhy2.main.domain.datasource.PromotionDataSource +import com.teamhy2.main.domain.repository.PromotionRepository import com.teamhy2.main.domain.repository.StudyDayRepository import com.teamhy2.main.domain.repository.WiseSayingRepository import dagger.Binds @@ -20,4 +24,10 @@ abstract class MainModule { @Binds @Singleton abstract fun bindStudyDayRepository(remoteStudyDayRepository: RemoteStudyDayRepository): StudyDayRepository + + @Binds + abstract fun bindPromotionDataSource(remoteConfigPromotionDataSource: RemoteConfigPromotionDataSource): PromotionDataSource + + @Binds + abstract fun bindPromotionRepository(defaultPromotionRepository: DefaultPromotionRepository): PromotionRepository } diff --git a/main-data/src/main/java/com/teamhy2/main/data/dto/PromotionDto.kt b/main-data/src/main/java/com/teamhy2/main/data/dto/PromotionDto.kt new file mode 100644 index 00000000..7df2f973 --- /dev/null +++ b/main-data/src/main/java/com/teamhy2/main/data/dto/PromotionDto.kt @@ -0,0 +1,12 @@ +package com.teamhy2.main.data.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class PromotionDto( + val imageUrl: String, + val detailUrl: String, + val startDate: String, + val endDate: String, + val isActive: Boolean = false, +) diff --git a/main-data/src/main/java/com/teamhy2/main/data/mapper/PromotionMapper.kt b/main-data/src/main/java/com/teamhy2/main/data/mapper/PromotionMapper.kt new file mode 100644 index 00000000..62391f55 --- /dev/null +++ b/main-data/src/main/java/com/teamhy2/main/data/mapper/PromotionMapper.kt @@ -0,0 +1,12 @@ +package com.teamhy2.main.data.mapper + +import com.teamhy2.main.data.dto.PromotionDto +import com.teamhy2.main.domain.model.Promotion + +fun PromotionDto.toDomain(): Promotion { + return Promotion( + imageUrl = this.imageUrl, + detailUrl = this.detailUrl, + isActive = this.isActive, + ) +} diff --git a/main-data/src/main/java/com/teamhy2/main/data/repository/DefaultPromotionRepository.kt b/main-data/src/main/java/com/teamhy2/main/data/repository/DefaultPromotionRepository.kt new file mode 100644 index 00000000..0ea2cd69 --- /dev/null +++ b/main-data/src/main/java/com/teamhy2/main/data/repository/DefaultPromotionRepository.kt @@ -0,0 +1,31 @@ +package com.teamhy2.main.data.repository + +import com.teamhy2.main.data.datastore.PromotionDataStore +import com.teamhy2.main.domain.datasource.PromotionDataSource +import com.teamhy2.main.domain.model.Promotion +import com.teamhy2.main.domain.repository.PromotionRepository +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate +import javax.inject.Inject + +class DefaultPromotionRepository + @Inject + constructor( + private val dataSource: PromotionDataSource, + private val dataStore: PromotionDataStore, + ) : PromotionRepository { + override val isPromotionDismissed: Flow + get() = dataStore.isPromotionDismissed + + override suspend fun fetchPromotionData(): Result { + return dataSource.fetchPromotionData() + } + + override suspend fun savePromotionDismissPeriod( + startDate: LocalDate, + endDate: LocalDate, + ): Result = + runCatching { + dataStore.savePromotionDismissPeriod(startDate, endDate) + } + } diff --git a/main-domain/src/main/java/com/teamhy2/main/domain/datasource/PromotionDataSource.kt b/main-domain/src/main/java/com/teamhy2/main/domain/datasource/PromotionDataSource.kt new file mode 100644 index 00000000..dcdc8a6c --- /dev/null +++ b/main-domain/src/main/java/com/teamhy2/main/domain/datasource/PromotionDataSource.kt @@ -0,0 +1,7 @@ +package com.teamhy2.main.domain.datasource + +import com.teamhy2.main.domain.model.Promotion + +interface PromotionDataSource { + suspend fun fetchPromotionData(): Result +} diff --git a/main-domain/src/main/java/com/teamhy2/main/domain/model/Promotion.kt b/main-domain/src/main/java/com/teamhy2/main/domain/model/Promotion.kt new file mode 100644 index 00000000..afdbb585 --- /dev/null +++ b/main-domain/src/main/java/com/teamhy2/main/domain/model/Promotion.kt @@ -0,0 +1,16 @@ +package com.teamhy2.main.domain.model + +data class Promotion( + val imageUrl: String, + val detailUrl: String, + val isActive: Boolean, +) { + companion object { + val DEFAULT = + Promotion( + imageUrl = "", + detailUrl = "", + isActive = false, + ) + } +} diff --git a/main-domain/src/main/java/com/teamhy2/main/domain/repository/PromotionRepository.kt b/main-domain/src/main/java/com/teamhy2/main/domain/repository/PromotionRepository.kt new file mode 100644 index 00000000..ca0338b5 --- /dev/null +++ b/main-domain/src/main/java/com/teamhy2/main/domain/repository/PromotionRepository.kt @@ -0,0 +1,16 @@ +package com.teamhy2.main.domain.repository + +import com.teamhy2.main.domain.model.Promotion +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate + +interface PromotionRepository { + val isPromotionDismissed: Flow + + suspend fun fetchPromotionData(): Result + + suspend fun savePromotionDismissPeriod( + startDate: LocalDate, + endDate: LocalDate, + ): Result +} diff --git a/main-domain/src/main/java/com/teamhy2/main/domain/util/DateUtil.kt b/main-domain/src/main/java/com/teamhy2/main/domain/util/DateUtil.kt new file mode 100644 index 00000000..ef43f686 --- /dev/null +++ b/main-domain/src/main/java/com/teamhy2/main/domain/util/DateUtil.kt @@ -0,0 +1,21 @@ +package com.teamhy2.main.domain.util + +import java.time.LocalDate + +object DateUtil { + /** + * Checks if the current date is within the specified date range. + * + * @param startDate The start date of the range. + * @param endDate The end date of the range. + * @return True if the current date is within the range, false otherwise. + */ + fun isTodayWithinDateRange( + startDate: LocalDate, + endDate: LocalDate, + ): Boolean { + val today: LocalDate = LocalDate.now() + return today.isEqual(startDate) || today.isEqual(endDate) || + (today.isAfter(startDate) && today.isBefore(endDate)) + } +} diff --git a/main-presentation/build.gradle.kts b/main-presentation/build.gradle.kts index 6bbe65e3..1c7d8769 100644 --- a/main-presentation/build.gradle.kts +++ b/main-presentation/build.gradle.kts @@ -49,4 +49,6 @@ dependencies { implementation(libs.accompanist.permissions) implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.navigation) + + implementation(libs.coil) } diff --git a/main-presentation/src/main/java/com/teamhy2/feature/home/HomeScreen.kt b/main-presentation/src/main/java/com/teamhy2/feature/home/HomeScreen.kt index ec721a5d..5a6921df 100644 --- a/main-presentation/src/main/java/com/teamhy2/feature/home/HomeScreen.kt +++ b/main-presentation/src/main/java/com/teamhy2/feature/home/HomeScreen.kt @@ -34,6 +34,7 @@ import com.teamhy2.designsystem.ui.theme.HY2Theme import com.teamhy2.designsystem.util.compositionlocal.LocalShowSnackBar import com.teamhy2.designsystem.util.compositionlocal.LocalTracker import com.teamhy2.feature.home.component.InitTimerComponent +import com.teamhy2.feature.home.component.PromotionDialog import com.teamhy2.feature.home.component.RunningTimerComponent import com.teamhy2.feature.home.component.WeeklyStudyCalendar import com.teamhy2.feature.home.model.HomeUiState @@ -44,6 +45,7 @@ import com.teamhy2.hongikyeolgong2.timer.presentation.model.TimerUiState import com.teamhy2.main.domain.model.WeeklyStudyDay import com.teamhy2.main.domain.model.WiseSaying import kotlinx.coroutines.flow.collectLatest +import java.time.LocalDate import java.time.LocalDateTime import java.time.temporal.ChronoUnit @@ -177,6 +179,27 @@ fun HomeRoute( ) } + if (uiState.isPromotionDialog) { + PromotionDialog( + promotionImageUrl = uiState.promotion.imageUrl, + onDetailClick = { + homeViewModel.updatePromotionDialogVisibility(false) + val intent = + Intent(Intent.ACTION_VIEW, Uri.parse(uiState.promotion.detailUrl)) + context.startActivity(intent) + }, + onCloseClick = { homeViewModel.updatePromotionDialogVisibility(false) }, + onCloseTodayClick = { + homeViewModel.updatePromotionDismissPeriod( + startDate = LocalDate.now(), + endDate = LocalDate.now(), + ) + homeViewModel.updatePromotionDialogVisibility(false) + }, + onDismiss = { homeViewModel.updatePromotionDialogVisibility(false) }, + ) + } + HomeScreen( weeklyStudyDays = uiState.weeklyStudyDays, wiseSaying = uiState.wiseSaying, diff --git a/main-presentation/src/main/java/com/teamhy2/feature/home/HomeViewModel.kt b/main-presentation/src/main/java/com/teamhy2/feature/home/HomeViewModel.kt index 34e4dc9c..086f4616 100644 --- a/main-presentation/src/main/java/com/teamhy2/feature/home/HomeViewModel.kt +++ b/main-presentation/src/main/java/com/teamhy2/feature/home/HomeViewModel.kt @@ -5,13 +5,16 @@ import androidx.lifecycle.viewModelScope import com.teamhy2.feature.home.model.HomeUiState import com.teamhy2.hongikyeolgong2.timer.model.TimerService import com.teamhy2.hongikyeolgong2.timer.presentation.model.TimerUiState +import com.teamhy2.main.domain.model.Promotion import com.teamhy2.main.domain.model.WeeklyStudyDay import com.teamhy2.main.domain.model.WiseSaying +import com.teamhy2.main.domain.repository.PromotionRepository import com.teamhy2.main.domain.repository.StudyDayRepository import com.teamhy2.main.domain.repository.WiseSayingRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -32,6 +35,7 @@ class HomeViewModel constructor( private val wiseSayingRepository: WiseSayingRepository, private val studyDayRepository: StudyDayRepository, + private val promotionRepository: PromotionRepository, private val timerService: TimerService, ) : ViewModel() { private val _homeUiState = MutableStateFlow(HomeUiState.Loading) @@ -42,6 +46,7 @@ class HomeViewModel init { loadHomeData() + loadPromotionData() } private fun loadHomeData() { @@ -67,6 +72,30 @@ class HomeViewModel } } + private fun loadPromotionData() { + viewModelScope.launch { + runCatching { + val promotion: Promotion = promotionRepository.fetchPromotionData().getOrThrow() + val isDismissedFlow: Flow = promotionRepository.isPromotionDismissed + + isDismissedFlow.collect { isDismissed -> + _homeUiState.update { currentState -> + if (currentState is HomeUiState.Success) { + currentState.copy( + promotion = promotion, + isPromotionDialog = !isDismissed && promotion.isActive, + ) + } else { + currentState + } + } + } + }.onFailure { exception -> + _errorFlow.emit(exception) + } + } + } + fun startTimerService( startDateTime: LocalDateTime, duration: Duration, @@ -175,4 +204,25 @@ class HomeViewModel } } } + + fun updatePromotionDismissPeriod( + startDate: LocalDate, + endDate: LocalDate, + ) { + viewModelScope.launch { + promotionRepository.savePromotionDismissPeriod(startDate, endDate) + .onFailure { exception -> + _errorFlow.emit(exception) + } + } + } + + fun updatePromotionDialogVisibility(isVisible: Boolean) { + _homeUiState.update { currentState -> + when (currentState) { + is HomeUiState.Success -> currentState.copy(isPromotionDialog = isVisible) + else -> currentState + } + } + } } diff --git a/main-presentation/src/main/java/com/teamhy2/feature/home/component/PromotionDialog.kt b/main-presentation/src/main/java/com/teamhy2/feature/home/component/PromotionDialog.kt new file mode 100644 index 00000000..8edf6170 --- /dev/null +++ b/main-presentation/src/main/java/com/teamhy2/feature/home/component/PromotionDialog.kt @@ -0,0 +1,144 @@ +package com.teamhy2.feature.home.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.DialogWindowProvider +import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import com.teamhy2.designsystem.ui.theme.Gray300 +import com.teamhy2.designsystem.ui.theme.HY2Theme + +@Composable +fun PromotionDialog( + promotionImageUrl: String, + onDetailClick: () -> Unit, + onCloseClick: () -> Unit, + onCloseTodayClick: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + val screenWidth: Dp = LocalConfiguration.current.screenWidthDp.dp + + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false), + ) { + (LocalView.current.parent as DialogWindowProvider).window.setDimAmount(0.75f) + Surface( + modifier = + modifier + .width(screenWidth - 30.dp * 2) + .wrapContentHeight(), + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Surface( + modifier = + Modifier + .fillMaxWidth() + .wrapContentHeight(), + shape = RoundedCornerShape(12.dp), + color = Color.Transparent, + ) { + AsyncImage( + model = promotionImageUrl, + contentDescription = null, + modifier = + Modifier + .fillMaxWidth() + .clickable { + onDetailClick() + }, + onState = { state -> + if (state is AsyncImagePainter.State.Error) { + onDismiss() + } + }, + ) + } + Spacer(modifier = Modifier.height(20.dp)) + Row( + modifier = + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + val interactionSource: MutableInteractionSource = + remember { MutableInteractionSource() } + + Text( + text = "오늘 그만 보기", + color = Gray300, + style = HY2Theme.typography.body05, + modifier = + Modifier + .weight(1f) + .clickable( + interactionSource = interactionSource, + indication = null, + ) { onCloseTodayClick() }, + textAlign = TextAlign.Center, + ) + Text( + text = "|", + color = Gray300, + style = HY2Theme.typography.body05, + textAlign = TextAlign.Center, + ) + Text( + text = "닫기", + color = Gray300, + style = HY2Theme.typography.body05, + modifier = + Modifier + .weight(1f) + .clickable( + interactionSource = interactionSource, + indication = null, + ) { onCloseClick() }, + textAlign = TextAlign.Center, + ) + } + } + } + } +} + +@Composable +@Preview(showBackground = true) +fun PromotionDialogPreview() { + HY2Theme { + PromotionDialog( + promotionImageUrl = "https://github.com/user-attachments/assets/e268f0b3-df44-4498-b23a-5325c1d6ef85", + onDetailClick = {}, + onCloseClick = {}, + onCloseTodayClick = {}, + onDismiss = {}, + ) + } +} diff --git a/main-presentation/src/main/java/com/teamhy2/feature/home/model/HomeUiState.kt b/main-presentation/src/main/java/com/teamhy2/feature/home/model/HomeUiState.kt index 5d12c13e..39987156 100644 --- a/main-presentation/src/main/java/com/teamhy2/feature/home/model/HomeUiState.kt +++ b/main-presentation/src/main/java/com/teamhy2/feature/home/model/HomeUiState.kt @@ -1,6 +1,7 @@ package com.teamhy2.feature.home.model import com.teamhy2.hongikyeolgong2.timer.presentation.model.TimerUiState +import com.teamhy2.main.domain.model.Promotion import com.teamhy2.main.domain.model.WeeklyStudyDay import com.teamhy2.main.domain.model.WiseSaying import java.time.LocalDateTime @@ -14,8 +15,10 @@ sealed interface HomeUiState { val isTimePickerVisible: Boolean = false, val isStudyRoomExtendDialog: Boolean = false, val isStudyRoomEndDialog: Boolean = false, + val isPromotionDialog: Boolean = false, val selectedTime: LocalDateTime = LocalDateTime.now(), val isTimerRunning: Boolean = false, + val promotion: Promotion = Promotion.DEFAULT, val timerUiState: TimerUiState = TimerUiState(), ) : HomeUiState