diff --git a/README.md b/README.md
index c49a1f46..4dc78720 100644
--- a/README.md
+++ b/README.md
@@ -29,18 +29,18 @@
떠내려온 편지를 통해 느린 소통의 감성을 경험해보세요.
## Features
-- ✨ Feature 1
-- ✨ Feature 2
-- ✨ Feature 3
+- ✨ 매일 오후 6시마다 알림을 받고 떠내려오는 보틀에 호감을 표시해보세요.
+- ✨ 호감을 받은 상대방이 수락하면 대화가 시작돼요.
+- ✨ 상대와 끝까지 핑퐁(대화)이 완료되면 카카오톡 연락처를 얻어 서로를 이어줄게요.
## Screenshots
## Architecture
@@ -51,7 +51,7 @@
-
+
## TechStack
@@ -63,7 +63,8 @@
- **Networking**: Retrofit / OkHttp
- **Database**: Proto-Datastore
- **Async**: Coroutines
-- **Others**: Coil / Lottie / Cloudy
+- **Debugging Tool**: Firebase
+- **Others**: Coil / Cloudy / Kakao-Sdk / FCM
## Contact & Contributor
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 564b794e..4923f88b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -75,6 +75,7 @@ dependencies {
implementation(projects.feat.pingPong)
implementation(projects.feat.splash)
implementation(projects.feat.report)
+ implementation(projects.feat.setting)
// Compose
implementation(libs.androidx.compose.activity)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4e835ad9..ce018f09 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
+ android:resource="@drawable/bottle_notification_icon" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/bottle_notification_icon.xml b/app/src/main/res/drawable/bottle_notification_icon.xml
new file mode 100644
index 00000000..d28a4e13
--- /dev/null
+++ b/app/src/main/res/drawable/bottle_notification_icon.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon.xml b/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon.xml
index 0742a7d7..b89ea6a0 100644
--- a/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon_round.xml b/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon_round.xml
index 0742a7d7..b89ea6a0 100644
--- a/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/bottle_app_icon_round.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/bottle_app_icon.webp b/app/src/main/res/mipmap-hdpi/bottle_app_icon.webp
index cb966431..b70c586f 100644
Binary files a/app/src/main/res/mipmap-hdpi/bottle_app_icon.webp and b/app/src/main/res/mipmap-hdpi/bottle_app_icon.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/bottle_app_icon_round.webp b/app/src/main/res/mipmap-hdpi/bottle_app_icon_round.webp
index 4ddadccd..6f3d3980 100644
Binary files a/app/src/main/res/mipmap-hdpi/bottle_app_icon_round.webp and b/app/src/main/res/mipmap-hdpi/bottle_app_icon_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/bottle_app_icon.webp b/app/src/main/res/mipmap-mdpi/bottle_app_icon.webp
index dfffeb5d..896ee939 100644
Binary files a/app/src/main/res/mipmap-mdpi/bottle_app_icon.webp and b/app/src/main/res/mipmap-mdpi/bottle_app_icon.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/bottle_app_icon_round.webp b/app/src/main/res/mipmap-mdpi/bottle_app_icon_round.webp
index 65661a96..481a909a 100644
Binary files a/app/src/main/res/mipmap-mdpi/bottle_app_icon_round.webp and b/app/src/main/res/mipmap-mdpi/bottle_app_icon_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/bottle_app_icon.webp b/app/src/main/res/mipmap-xhdpi/bottle_app_icon.webp
index 50fc8ce1..8f55264f 100644
Binary files a/app/src/main/res/mipmap-xhdpi/bottle_app_icon.webp and b/app/src/main/res/mipmap-xhdpi/bottle_app_icon.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/bottle_app_icon_round.webp b/app/src/main/res/mipmap-xhdpi/bottle_app_icon_round.webp
index 807aa443..8d878938 100644
Binary files a/app/src/main/res/mipmap-xhdpi/bottle_app_icon_round.webp and b/app/src/main/res/mipmap-xhdpi/bottle_app_icon_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/bottle_app_icon.webp b/app/src/main/res/mipmap-xxhdpi/bottle_app_icon.webp
index 44d6df6a..ede4f70b 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/bottle_app_icon.webp and b/app/src/main/res/mipmap-xxhdpi/bottle_app_icon.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/bottle_app_icon_round.webp b/app/src/main/res/mipmap-xxhdpi/bottle_app_icon_round.webp
index 3d4c436b..fbceaf60 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/bottle_app_icon_round.webp and b/app/src/main/res/mipmap-xxhdpi/bottle_app_icon_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon.webp b/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon.webp
index 06966e22..e1cb45ed 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon.webp and b/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon_round.webp b/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon_round.webp
index 0e89057b..7804c245 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon_round.webp and b/app/src/main/res/mipmap-xxxhdpi/bottle_app_icon_round.webp differ
diff --git a/app/src/main/res/values/bottle_app_icon_background.xml b/app/src/main/res/values/bottle_app_icon_background.xml
new file mode 100644
index 00000000..28337c0a
--- /dev/null
+++ b/app/src/main/res/values/bottle_app_icon_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/string.xml b/app/src/main/res/values/string.xml
index a340b46a..b7b93f95 100644
--- a/app/src/main/res/values/string.xml
+++ b/app/src/main/res/values/string.xml
@@ -1,5 +1,5 @@
fcm_channel_id
- fcm_channel_name
+ 푸쉬 알림
\ No newline at end of file
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
index 050975d3..4de0bc26 100644
--- a/core/data/build.gradle.kts
+++ b/core/data/build.gradle.kts
@@ -11,6 +11,7 @@ dependencies {
implementation(projects.core.domain)
implementation(projects.core.network)
implementation(projects.core.datastore)
+ implementation(projects.core.local)
implementation(libs.jakewharton.timber)
}
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/AlimyResponseMapper.kt b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/AlimyResponseMapper.kt
new file mode 100644
index 00000000..e1b332c0
--- /dev/null
+++ b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/AlimyResponseMapper.kt
@@ -0,0 +1,20 @@
+package com.team.bottles.core.data.mapper
+
+import com.team.bottles.core.domain.user.model.Notification
+import com.team.bottles.core.domain.user.model.NotificationType
+import com.team.bottles.network.dto.user.response.AlimyResponse
+import com.team.bottles.network.dto.user.response.AlimyType
+
+fun AlimyResponse.toNotification(): Notification =
+ Notification(
+ notificationType = this.alimyType.toNotificationType(),
+ enabled = this.enabled
+ )
+
+fun AlimyType.toNotificationType(): NotificationType =
+ when (this) {
+ AlimyType.MARKETING -> NotificationType.MARKETING
+ AlimyType.DAILY_RANDOM -> NotificationType.DAILY_RANDOM
+ AlimyType.PING_PONG -> NotificationType.PING_PONG
+ AlimyType.RECEIVE_LIKE -> NotificationType.RECEIVE_LIKE
+ }
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/NotificationTypeMapper.kt b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/NotificationTypeMapper.kt
new file mode 100644
index 00000000..2bf438ad
--- /dev/null
+++ b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/NotificationTypeMapper.kt
@@ -0,0 +1,12 @@
+package com.team.bottles.core.data.mapper
+
+import com.team.bottles.core.domain.user.model.NotificationType
+import com.team.bottles.network.dto.user.response.AlimyType
+
+fun NotificationType.toAlimyType(): AlimyType =
+ when (this) {
+ NotificationType.PING_PONG -> AlimyType.PING_PONG
+ NotificationType.MARKETING -> AlimyType.MARKETING
+ NotificationType.DAILY_RANDOM -> AlimyType.DAILY_RANDOM
+ NotificationType.RECEIVE_LIKE -> AlimyType.RECEIVE_LIKE
+ }
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/UpdateAppVersionResponseMapper.kt b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/UpdateAppVersionResponseMapper.kt
new file mode 100644
index 00000000..1e691fc4
--- /dev/null
+++ b/core/data/src/main/kotlin/com/team/bottles/core/data/mapper/UpdateAppVersionResponseMapper.kt
@@ -0,0 +1,9 @@
+package com.team.bottles.core.data.mapper
+
+import com.team.bottles.network.dto.auth.response.UpdateAppVersionResponse
+
+fun UpdateAppVersionResponse.toLatestVersionCode(): Int =
+ this.latestAndroidVersion?: 10007
+
+fun UpdateAppVersionResponse.toMinimumVersionCode(): Int =
+ this.minimumAndroidVersion?: 10007
\ No newline at end of file
diff --git a/core/data/src/main/kotlin/com/team/bottles/core/data/repository/AuthRepositoryImpl.kt b/core/data/src/main/kotlin/com/team/bottles/core/data/repository/AuthRepositoryImpl.kt
index f29c6347..d4de122a 100644
--- a/core/data/src/main/kotlin/com/team/bottles/core/data/repository/AuthRepositoryImpl.kt
+++ b/core/data/src/main/kotlin/com/team/bottles/core/data/repository/AuthRepositoryImpl.kt
@@ -1,6 +1,8 @@
package com.team.bottles.core.data.repository
import com.team.bottles.core.data.mapper.toAuthResult
+import com.team.bottles.core.data.mapper.toLatestVersionCode
+import com.team.bottles.core.data.mapper.toMinimumVersionCode
import com.team.bottles.core.datastore.datasource.TokenDataSource
import com.team.bottles.core.domain.auth.model.AuthResult
import com.team.bottles.core.domain.auth.model.Token
@@ -73,4 +75,10 @@ class AuthRepositoryImpl @Inject constructor(
override suspend fun getSavedLocalFcmToken(): String =
tokenDataSource.getFcmDeviceToken()
+ override suspend fun getLatestAppVersion(): Int =
+ authDataSource.fetchRequiredMinimumAppVersion().toLatestVersionCode()
+
+ override suspend fun getRequiredAppVersion(): Int =
+ authDataSource.fetchRequiredMinimumAppVersion().toMinimumVersionCode()
+
}
diff --git a/core/data/src/main/kotlin/com/team/bottles/core/data/repository/UserRepositoryImpl.kt b/core/data/src/main/kotlin/com/team/bottles/core/data/repository/UserRepositoryImpl.kt
index d0c3b4b3..ee53d8ae 100644
--- a/core/data/src/main/kotlin/com/team/bottles/core/data/repository/UserRepositoryImpl.kt
+++ b/core/data/src/main/kotlin/com/team/bottles/core/data/repository/UserRepositoryImpl.kt
@@ -1,12 +1,19 @@
package com.team.bottles.core.data.repository
+import com.team.bottles.core.data.mapper.toAlimyType
+import com.team.bottles.core.data.mapper.toNotification
+import com.team.bottles.core.domain.user.model.Notification
import com.team.bottles.core.domain.user.repository.UserRepository
+import com.team.bottles.local.datasource.DeviceDataSource
import com.team.bottles.network.datasource.UserDataSource
+import com.team.bottles.network.dto.auth.request.BlockContactListRequest
+import com.team.bottles.network.dto.user.request.AlimyOnOffRequest
import com.team.bottles.network.dto.user.request.ReportUserRequest
import javax.inject.Inject
class UserRepositoryImpl @Inject constructor(
private val userDataSource: UserDataSource,
+ private val deviceDataSource: DeviceDataSource,
) : UserRepository {
override suspend fun reportUser(userId: Int, contents: String) {
@@ -18,4 +25,29 @@ class UserRepositoryImpl @Inject constructor(
)
}
+ override suspend fun loadContacts(): List =
+ deviceDataSource.getContacts()
+
+ override suspend fun updateBlockingContacts(contacts: List) {
+ userDataSource.updateWantToBlockContacts(
+ request = BlockContactListRequest(
+ blockContacts = contacts
+ )
+ )
+ }
+
+ override suspend fun loadSettingNotifications(): List =
+ userDataSource.fetchSettingNotifications().map { response ->
+ response.toNotification()
+ }
+
+ override suspend fun updateSettingNotification(notification: Notification) {
+ userDataSource.updateSettingNotification(
+ request = AlimyOnOffRequest(
+ alimyType = notification.notificationType.toAlimyType(),
+ enabled = notification.enabled
+ )
+ )
+ }
+
}
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/team/bottles/core/datastore/di/LocalDataSourceModule.kt b/core/datastore/src/main/java/com/team/bottles/core/datastore/di/DataStoreDataSourceModule.kt
similarity index 91%
rename from core/datastore/src/main/java/com/team/bottles/core/datastore/di/LocalDataSourceModule.kt
rename to core/datastore/src/main/java/com/team/bottles/core/datastore/di/DataStoreDataSourceModule.kt
index b04e1a81..76bab67b 100644
--- a/core/datastore/src/main/java/com/team/bottles/core/datastore/di/LocalDataSourceModule.kt
+++ b/core/datastore/src/main/java/com/team/bottles/core/datastore/di/DataStoreDataSourceModule.kt
@@ -9,7 +9,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
-abstract class LocalDataSourceModule {
+abstract class DataStoreDataSourceModule {
@Binds
abstract fun bindsTokenDataSource(dataSourceImpl: TokenDataSourceImpl): TokenDataSource
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/BottomBar.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/BottomBar.kt
index 10d3079b..788fc4ea 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/BottomBar.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/BottomBar.kt
@@ -3,6 +3,7 @@ package com.team.bottles.core.designsystem.components.bars
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -18,16 +19,14 @@ fun BottlesBottomBar(
modifier: Modifier = Modifier,
text: String,
onClick: () -> Unit,
- enabled: Boolean = false
+ enabled: Boolean = false,
+ isDebounce: Boolean = true,
) {
Box(
modifier = modifier
+ .height(height = 88.dp)
.background(brush = BottlesTheme.color.background.tertiary)
- .padding(
- top = 24.dp,
- start = 16.dp,
- end = 16.dp
- ),
+ .padding(horizontal = BottlesTheme.spacing.medium),
contentAlignment = Alignment.BottomCenter
) {
BottlesSolidButton(
@@ -36,7 +35,7 @@ fun BottlesBottomBar(
text = text,
enabled = enabled,
onClick = onClick,
- isDebounce = true,
+ isDebounce = isDebounce,
)
}
}
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/TopBar.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/TopBar.kt
index 9a6c26fb..2e950b60 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/TopBar.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/bars/TopBar.kt
@@ -29,12 +29,13 @@ fun BottlesTopBar(
Box(
modifier = modifier
.fillMaxWidth()
- .height(48.dp)
- .padding(horizontal = 16.dp),
+ .height(48.dp),
contentAlignment = Alignment.Center,
) {
Row(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = BottlesTheme.spacing.medium),
horizontalArrangement = if (leadingIcon == null) Arrangement.End else Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/IconButton.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/IconButton.kt
index 23716d06..2f048b21 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/IconButton.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/IconButton.kt
@@ -8,11 +8,13 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -39,13 +41,14 @@ fun BottlesIconButtonWithText(
color = BottlesTheme.color.border.enabled,
shape = BottlesTheme.shape.extraSmall
)
- .padding(
- horizontal = BottlesTheme.spacing.small,
- vertical = 7.5f.dp
- )
+ .clip(shape = BottlesTheme.shape.extraSmall)
.noRippleClickable(
onClick = onClick,
enabled = enabled
+ )
+ .padding(
+ horizontal = BottlesTheme.spacing.small,
+ vertical = 7.5f.dp
),
horizontalArrangement = Arrangement.spacedBy(
space = BottlesTheme.spacing.doubleExtraSmall,
@@ -66,29 +69,46 @@ fun BottlesIconButtonWithText(
}
}
+enum class IconButtonType {
+ CIRCLE,
+ RECTANGLE,
+ ;
+}
+
@Composable
fun BottlesIconButton(
modifier: Modifier = Modifier,
+ iconButtonType: IconButtonType,
@DrawableRes icon: Int,
onClick: () -> Unit,
enabled: Boolean = true
) {
+ val shape = when (iconButtonType) {
+ IconButtonType.RECTANGLE -> BottlesTheme.shape.extraSmall
+ IconButtonType.CIRCLE -> CircleShape
+ }
+ val padding = when (iconButtonType) {
+ IconButtonType.RECTANGLE -> 10.dp
+ IconButtonType.CIRCLE -> 6.dp
+ }
+
Box(
modifier = modifier
.background(
color = BottlesTheme.color.container.enabledPrimary,
- shape = BottlesTheme.shape.extraSmall
+ shape = shape
)
.border(
width = 1.dp,
color = BottlesTheme.color.border.enabled,
- shape = BottlesTheme.shape.extraSmall
+ shape = shape
)
- .padding(10.dp)
+ .clip(shape = shape)
.noRippleClickable(
onClick = onClick,
enabled = enabled
)
+ .padding(all = padding)
) {
Icon(
painter = painterResource(id = icon),
@@ -98,13 +118,14 @@ fun BottlesIconButton(
}
}
-@Preview
+@Preview(showBackground = true)
@Composable
private fun BottlesIconButtonPreview() {
BottlesTheme {
- Column {
+ Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
BottlesIconButton(
icon = R.drawable.ic_close_16,
+ iconButtonType = IconButtonType.RECTANGLE,
onClick = {}
)
BottlesIconButtonWithText(
@@ -112,6 +133,11 @@ private fun BottlesIconButtonPreview() {
icon = R.drawable.ic_group_14,
onClick = {}
)
+ BottlesIconButton(
+ icon = R.drawable.ic_pencil_12,
+ iconButtonType = IconButtonType.CIRCLE,
+ onClick = {}
+ )
}
}
}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/SolidButton.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/SolidButton.kt
index d6a3f984..b0cde580 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/SolidButton.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/buttons/SolidButton.kt
@@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
@@ -25,8 +26,7 @@ import com.team.bottles.core.designsystem.modifier.debounceClickable
import com.team.bottles.core.designsystem.theme.BottlesTheme
enum class SolidButtonType(val height: Dp) {
- XS(height = 36.dp),
- SM(height = 56.dp),
+ SM(height = 36.dp),
MD(height = 56.dp),
LG(height = 64.dp),
;
@@ -46,8 +46,15 @@ fun BottlesSolidButton(
val isPressed by interactionSource.collectIsPressedAsState()
val shape = when(buttonType) {
- SolidButtonType.XS -> BottlesTheme.shape.extraSmall
- else -> BottlesTheme.shape.small
+ SolidButtonType.SM -> BottlesTheme.shape.extraSmall
+ SolidButtonType.MD -> BottlesTheme.shape.small
+ SolidButtonType.LG -> BottlesTheme.shape.medium
+ }
+
+ val backgroundColor = when {
+ !enabled -> BottlesTheme.color.container.disabledSecondary
+ isPressed -> BottlesTheme.color.container.pressed
+ else -> BottlesTheme.color.container.enabledSecondary
}
SolidButton(
@@ -55,9 +62,9 @@ fun BottlesSolidButton(
onClick = onClick,
enabled = enabled,
shape = shape,
+ backgroundColor = backgroundColor,
isDebounce = isDebounce,
interactionSource = interactionSource,
- isPressed = isPressed,
buttonType = buttonType,
contentHorizontalPadding = contentHorizontalPadding
) {
@@ -68,7 +75,7 @@ fun BottlesSolidButton(
}
val textStyle = when(buttonType) {
- SolidButtonType.XS -> BottlesTheme.typography.body
+ SolidButtonType.SM -> BottlesTheme.typography.body
else -> BottlesTheme.typography.subTitle1
}
@@ -86,19 +93,13 @@ fun SolidButton(
onClick: () -> Unit,
enabled: Boolean,
shape: Shape,
+ backgroundColor: Color,
buttonType: SolidButtonType,
contentHorizontalPadding: Dp,
isDebounce: Boolean,
interactionSource: MutableInteractionSource,
- isPressed: Boolean,
content: @Composable () -> Unit,
) {
- val backgroundColor = when {
- !enabled -> BottlesTheme.color.container.disabledSecondary
- isPressed -> BottlesTheme.color.container.pressed
- else -> BottlesTheme.color.container.enabledSecondary
- }
-
Box(
modifier = modifier
.height(height = buttonType.height)
@@ -107,11 +108,6 @@ fun SolidButton(
shape = shape
)
.clip(shape = shape)
- .padding(
- paddingValues = PaddingValues(
- horizontal = contentHorizontalPadding,
- ),
- )
.then(
if (isDebounce) {
Modifier.debounceClickable(
@@ -128,6 +124,11 @@ fun SolidButton(
indication = null
)
}
+ )
+ .padding(
+ paddingValues = PaddingValues(
+ horizontal = contentHorizontalPadding,
+ ),
),
contentAlignment = Alignment.Center
) {
@@ -142,12 +143,6 @@ fun SolidButton(
private fun SolidButtonPreview() {
BottlesTheme {
Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
- BottlesSolidButton(
- buttonType = SolidButtonType.XS,
- text = "Text",
- onClick = { },
- contentHorizontalPadding = 12.dp
- )
BottlesSolidButton(
buttonType = SolidButtonType.SM,
text = "Text",
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/cards/Card.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/cards/Card.kt
new file mode 100644
index 00000000..e59278d9
--- /dev/null
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/cards/Card.kt
@@ -0,0 +1,63 @@
+package com.team.bottles.core.designsystem.components.cards
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithArrow
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+fun BottlesCard(
+ modifier: Modifier = Modifier,
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+ contents: @Composable () -> Unit
+) {
+ Column(
+ modifier = modifier
+ .background(
+ color = BottlesTheme.color.container.primary,
+ shape = BottlesTheme.shape.extraLarge
+ )
+ .border(
+ width = 1.dp,
+ shape = BottlesTheme.shape.extraLarge,
+ color = BottlesTheme.color.border.primary
+ )
+ .clip(shape = BottlesTheme.shape.extraLarge)
+ .padding(
+ vertical = BottlesTheme.spacing.extraLarge,
+ horizontal = BottlesTheme.spacing.medium
+ ),
+ verticalArrangement = verticalArrangement
+ ) {
+ contents.invoke()
+ }
+}
+
+@Preview
+@Composable
+private fun BottlesCardPreview() {
+ BottlesTheme {
+ BottlesCard(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.extraLarge
+ )
+ ) {
+ BottlesSettingItemWithArrow(
+ title = "프로필 수정",
+ onClickItem = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Profile.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Profile.kt
new file mode 100644
index 00000000..477740ea
--- /dev/null
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Profile.kt
@@ -0,0 +1,143 @@
+package com.team.bottles.core.designsystem.components.etc
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.skydoves.cloudy.cloudy
+import com.skydoves.landscapist.ImageOptions
+import com.skydoves.landscapist.coil.CoilImage
+import com.team.bottles.core.designsystem.R
+import com.team.bottles.core.designsystem.modifier.debounceNoRippleClickable
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+enum class ProfileImageType(val size: Dp) {
+ SM(size = 40.dp),
+ LG(size = 80.dp),
+ ;
+}
+
+@Composable
+fun BottlesProfileEdit(
+ modifier: Modifier = Modifier,
+ onClickImage: () -> Unit,
+ imageUrl: String,
+ profileImageType: ProfileImageType = ProfileImageType.LG,
+) {
+ Box(
+ modifier = modifier
+ .debounceNoRippleClickable(onClick = onClickImage)
+ ) {
+ BottlesProfile(
+ imageUrl = imageUrl,
+ profileImageType = profileImageType,
+ isBlur = false
+ )
+ Box(
+ modifier = modifier
+ .align(Alignment.BottomEnd)
+ .offset(x = 5.dp)
+ .background(
+ color = BottlesTheme.color.container.enabledPrimary,
+ shape = CircleShape
+ )
+ .border(
+ width = 1.dp,
+ color = BottlesTheme.color.border.enabled,
+ shape = CircleShape
+ )
+ .clip(shape = CircleShape)
+ .padding(all = 6.dp),
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_pencil_12),
+ contentDescription = null,
+ tint = BottlesTheme.color.icon.primary
+ )
+ }
+ }
+}
+
+@Composable
+fun BottlesProfile(
+ modifier: Modifier = Modifier,
+ imageUrl: String,
+ profileImageType: ProfileImageType,
+ isBlur: Boolean = true
+) {
+ CoilImage(
+ modifier = modifier
+ .size(size = profileImageType.size)
+ .clip(shape = CircleShape)
+ .then(
+ if (isBlur) {
+ Modifier.cloudy(radius = 5)
+ } else {
+ Modifier
+ }
+ ),
+ imageModel = { imageUrl },
+ previewPlaceholder = painterResource(id = R.drawable.sample_image),
+ imageOptions = ImageOptions(
+ contentScale = ContentScale.Crop
+ ),
+ loading = {
+ Box(
+ modifier = Modifier
+ .size(80.dp)
+ .background(
+ color = BottlesTheme.color.icon.secondary,
+ shape = CircleShape
+ )
+ .clip(shape = CircleShape)
+ )
+ },
+ failure = {
+ Box(
+ modifier = Modifier
+ .size(80.dp)
+ .background(
+ color = BottlesTheme.color.icon.secondary,
+ shape = CircleShape
+ )
+ .clip(shape = CircleShape)
+ )
+ }
+ )
+}
+
+@Preview
+@Composable
+private fun ProfileEditPreview() {
+ BottlesTheme {
+ Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
+ BottlesProfileEdit(
+ onClickImage = { /*TODO*/ },
+ imageUrl = ""
+ )
+ BottlesProfile(
+ imageUrl = "",
+ profileImageType = ProfileImageType.LG
+ )
+ BottlesProfile(
+ imageUrl = "",
+ profileImageType = ProfileImageType.SM
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Toggle.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Toggle.kt
new file mode 100644
index 00000000..92708123
--- /dev/null
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/Toggle.kt
@@ -0,0 +1,78 @@
+package com.team.bottles.core.designsystem.components.etc
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.modifier.noRippleClickable
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+fun BottlesToggleButton(
+ modifier: Modifier = Modifier,
+ checked: Boolean,
+ onCheckedChange: () -> Unit,
+ enabled: Boolean = true,
+) {
+ val backgroundColor = if (checked) {
+ BottlesTheme.color.icon.selected
+ } else {
+ BottlesTheme.color.icon.disabled
+ }
+ val shape = RoundedCornerShape(100.dp)
+
+ Box(
+ modifier = modifier
+ .width(44.dp)
+ .background(
+ color = backgroundColor,
+ shape = shape
+ )
+ .clip(shape = shape)
+ .padding(2.dp)
+ .noRippleClickable(
+ onClick = onCheckedChange,
+ enabled = enabled
+ ),
+ contentAlignment = if (checked) Alignment.TopEnd else Alignment.TopStart
+ ) {
+ Canvas(
+ modifier = Modifier
+ .size(size = 22.dp)
+ ) {
+ drawCircle(color = Color.White)
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun BottlesToggleButtonPreview() {
+ BottlesTheme {
+ Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
+ BottlesToggleButton(
+ checked = false,
+ onCheckedChange = {}
+ )
+ BottlesToggleButton(
+ checked = true,
+ onCheckedChange = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/UserInfo.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/UserInfo.kt
index fd018d54..1b497528 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/UserInfo.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/etc/UserInfo.kt
@@ -4,32 +4,25 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.skydoves.cloudy.cloudy
-import com.skydoves.landscapist.ImageOptions
-import com.skydoves.landscapist.coil.CoilImage
import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.theme.BottlesTheme
@Composable
-fun UserInfo(
+fun BottlesUserInfo(
modifier: Modifier = Modifier,
+ onClickImage: () -> Unit,
imageUrl: String,
userName: String,
userAge: Int,
- isBlur: Boolean = true
+ profileImageType: ProfileImageType = ProfileImageType.LG
) {
Column(
modifier = modifier.fillMaxWidth(),
@@ -38,29 +31,64 @@ fun UserInfo(
),
horizontalAlignment = Alignment.CenterHorizontally
) {
- CoilImage(
- modifier = Modifier
- .size(80.dp)
- .clip(shape = CircleShape)
- .then(
- if (isBlur) {
- Modifier.cloudy(radius = 5)
- } else {
- Modifier
- }
- ),
- imageModel = { imageUrl },
- previewPlaceholder = painterResource(id = R.drawable.sample_image),
- imageOptions = ImageOptions(
- contentScale = ContentScale.Crop
- ),
- loading = {
+ BottlesProfileEdit(
+ imageUrl = imageUrl,
+ profileImageType = profileImageType,
+ onClickImage = onClickImage
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.extraSmall
+ )
+ ) {
+ Text(
+ text = userName,
+ style = BottlesTheme.typography.subTitle1,
+ color = BottlesTheme.color.text.secondary
+ )
- },
- failure = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_spacing_bar_3_15),
+ contentDescription = null,
+ tint = BottlesTheme.color.border.secondary
+ )
+
+ Text(
+ text = stringResource(
+ id = R.string.user_age_user_info,
+ formatArgs = arrayOf(userAge)
+ ),
+ style = BottlesTheme.typography.body,
+ color = BottlesTheme.color.text.secondary
+ )
+ }
+ }
+}
- }
+@Composable
+fun BottlesUserInfo(
+ modifier: Modifier = Modifier,
+ imageUrl: String,
+ userName: String,
+ userAge: Int,
+ isBlur: Boolean = true,
+ profileImageType: ProfileImageType = ProfileImageType.LG
+) {
+ Column(
+ modifier = modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.small
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ BottlesProfile(
+ imageUrl = imageUrl,
+ profileImageType = profileImageType,
+ isBlur = isBlur
)
+
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
@@ -72,11 +100,13 @@ fun UserInfo(
style = BottlesTheme.typography.subTitle1,
color = BottlesTheme.color.text.secondary
)
+
Icon(
painter = painterResource(id = R.drawable.ic_spacing_bar_3_15),
contentDescription = null,
tint = BottlesTheme.color.border.secondary
)
+
Text(
text = stringResource(
id = R.string.user_age_user_info,
@@ -93,10 +123,23 @@ fun UserInfo(
@Composable
private fun UserInfoPreview() {
BottlesTheme {
- UserInfo(
+ BottlesUserInfo(
+ imageUrl = "",
+ userName = "냥냥이",
+ userAge = 15,
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun UserInfoEditPreview() {
+ BottlesTheme {
+ BottlesUserInfo(
imageUrl = "",
userName = "냥냥이",
- userAge = 15
+ userAge = 15,
+ onClickImage = {}
)
}
}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/lists/SettingItem.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/lists/SettingItem.kt
new file mode 100644
index 00000000..241e4f7f
--- /dev/null
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/lists/SettingItem.kt
@@ -0,0 +1,226 @@
+package com.team.bottles.core.designsystem.components.lists
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+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.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.R
+import com.team.bottles.core.designsystem.components.buttons.BottlesOutLinedButton
+import com.team.bottles.core.designsystem.components.buttons.OutlinedButtonType
+import com.team.bottles.core.designsystem.components.etc.BottlesToggleButton
+import com.team.bottles.core.designsystem.modifier.noRippleClickable
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+fun BottlesSettingItemWithButton(
+ modifier: Modifier = Modifier,
+ title: String,
+ subTitle: String,
+ onClickButton: () -> Unit,
+ buttonText: String,
+) {
+ SettingItem(
+ modifier = modifier,
+ leadingTitle = {
+ SettingItemTitleAndSubTitle(title = title, subTitle = subTitle)
+ },
+ trailingComponent = {
+ BottlesOutLinedButton(
+ text = buttonText,
+ buttonType = OutlinedButtonType.SM,
+ onClick = onClickButton,
+ contentHorizontalPadding = BottlesTheme.spacing.small
+ )
+ }
+ )
+}
+
+@Composable
+fun BottlesSettingItemWithToggleButton(
+ modifier: Modifier = Modifier,
+ title: String,
+ subTitle: String? = null,
+ checked: Boolean,
+ onCheckedChange: () -> Unit,
+ enabled: Boolean = true
+) {
+ SettingItem(
+ modifier = modifier,
+ leadingTitle = {
+ if (subTitle == null) {
+ SettingItemSingleTitle(title = title)
+ } else {
+ SettingItemTitleAndSubTitle(title = title, subTitle = subTitle)
+ }
+ },
+ trailingComponent = {
+ BottlesToggleButton(
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ enabled = enabled
+ )
+ }
+ )
+}
+
+@Composable
+fun BottlesSettingItemWithArrow(
+ modifier: Modifier = Modifier,
+ title: String,
+ subTitle: String? = null,
+ onClickItem: () -> Unit
+) {
+ SettingItem(
+ modifier = modifier.noRippleClickable(onClick = onClickItem),
+ leadingTitle = {
+ if (subTitle == null) {
+ SettingItemSingleTitle(title = title)
+ } else {
+ SettingItemTitleAndSubTitle(title = title, subTitle = subTitle)
+ }
+ },
+ trailingComponent = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_right_16),
+ contentDescription = null,
+ tint = BottlesTheme.color.icon.primary
+ )
+ }
+ )
+}
+
+@Composable
+fun BottlesSettingItem(
+ modifier: Modifier = Modifier,
+ title: String,
+ subTitle: String,
+) {
+ SettingItem(
+ modifier = modifier,
+ leadingTitle = {
+ SettingItemTitleAndSubTitle(title = title, subTitle = subTitle)
+ },
+ )
+}
+
+@Composable
+private fun RowScope.SettingItemSingleTitle(
+ modifier: Modifier = Modifier,
+ title: String,
+) {
+ Box(
+ modifier = modifier
+ .weight(weight = 1f)
+ .height(height = 26.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Text(
+ text = title,
+ style = BottlesTheme.typography.subTitle2,
+ color = BottlesTheme.color.text.secondary
+ )
+ }
+}
+
+@Composable
+private fun RowScope.SettingItemTitleAndSubTitle(
+ modifier: Modifier = Modifier,
+ title: String,
+ subTitle: String
+) {
+ Column(
+ modifier = modifier
+ .weight(weight = 1f),
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.extraSmall
+ )
+ ) {
+ Text(
+ text = title,
+ style = BottlesTheme.typography.subTitle2,
+ color = BottlesTheme.color.text.secondary
+ )
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = subTitle,
+ style = BottlesTheme.typography.caption,
+ color = BottlesTheme.color.text.tertiary
+ )
+ }
+}
+
+@Composable
+private fun SettingItem(
+ modifier: Modifier = Modifier,
+ leadingTitle: @Composable (RowScope.() -> Unit)? = null,
+ trailingComponent: @Composable (RowScope.() -> Unit)? = null,
+) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (leadingTitle != null) {
+ leadingTitle()
+ }
+
+ if (leadingTitle != null && trailingComponent != null) {
+ Spacer(modifier = Modifier.width(width = BottlesTheme.spacing.extraSmall))
+ }
+
+ if (trailingComponent != null) {
+ trailingComponent()
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun BottlesListTitleAndArrowPreview() {
+ BottlesTheme {
+ Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+ BottlesSettingItemWithArrow(
+ title = "text",
+ onClickItem = {}
+ )
+ BottlesSettingItemWithArrow(
+ title = "text",
+ subTitle = "subText",
+ onClickItem = {}
+ )
+ BottlesSettingItemWithToggleButton(
+ title = "text",
+ checked = true,
+ onCheckedChange = {}
+ )
+ BottlesSettingItemWithToggleButton(
+ title = "text",
+ subTitle = "subText",
+ checked = true,
+ onCheckedChange = {}
+ )
+ BottlesSettingItemWithButton(
+ title = "text",
+ subTitle = "subText",
+ onClickButton = { /*TODO*/ },
+ buttonText = "Text"
+ )
+ BottlesSettingItem(
+ title = "text",
+ subTitle = "subText",
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/popup/BalloonPopup.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/popup/BalloonPopup.kt
index 56c20218..0346f6d3 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/popup/BalloonPopup.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/components/popup/BalloonPopup.kt
@@ -15,13 +15,19 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.components.buttons.BottlesSolidButton
import com.team.bottles.core.designsystem.components.buttons.SolidButtonType
+import com.team.bottles.core.designsystem.components.etc.chips.BottlesChip
import com.team.bottles.core.designsystem.theme.BottlesTheme
@Composable
@@ -31,17 +37,18 @@ fun BottlesBalloonPopup(
) {
val shape = RoundedCornerShape(20.dp)
- Column(modifier = modifier) {
+ Column(modifier = modifier.shadow(4.dp, shape)) {
Box(
modifier = Modifier
+ .height(height = 42.dp)
.background(
color = BottlesTheme.color.container.primary,
shape = shape
)
.padding(
horizontal = BottlesTheme.spacing.large,
- vertical = BottlesTheme.spacing.small
- )
+ ),
+ contentAlignment = Alignment.Center
) {
Text(
text = text,
@@ -55,7 +62,7 @@ fun BottlesBalloonPopup(
.offset(y = (-0.2).dp),
painter = painterResource(id = R.drawable.ic_balloon_vertex_10_6),
contentDescription = null,
- tint = BottlesTheme.color.border.primary
+ tint = BottlesTheme.color.container.primary
)
}
}
@@ -67,17 +74,20 @@ fun BottlesBalloonPopup(
) {
val shape = RoundedCornerShape(20.dp)
- Column(modifier = modifier) {
+ Column(
+ modifier = modifier.shadow(4.dp, shape)
+ ) {
Box(
modifier = Modifier
+ .height(height = 42.dp)
.background(
color = BottlesTheme.color.container.primary,
shape = shape
)
.padding(
horizontal = BottlesTheme.spacing.large,
- vertical = BottlesTheme.spacing.small
- )
+ ),
+ contentAlignment = Alignment.Center
) {
Text(
text = text,
@@ -105,7 +115,7 @@ fun BottlesBalloonPopupWithButton(
) {
val shape = RoundedCornerShape(20.dp)
- Column(modifier = modifier) {
+ Column(modifier = modifier.shadow(8.dp, shape)) {
Column(
modifier = Modifier
.background(
@@ -122,7 +132,7 @@ fun BottlesBalloonPopupWithButton(
)
Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.small))
BottlesSolidButton(
- buttonType = SolidButtonType.XS,
+ buttonType = SolidButtonType.SM,
text = buttonText,
onClick = onClick,
contentHorizontalPadding = 12.dp
@@ -139,20 +149,50 @@ fun BottlesBalloonPopupWithButton(
}
}
+@Composable
+fun BottlesBalloonPopupWithChip(
+ modifier: Modifier = Modifier,
+ text: String,
+ count: Int
+) {
+ Box(modifier = modifier) {
+ BottlesBalloonPopup(
+ text = text
+ )
+ BottlesChip(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .offset(y = (-12).dp),
+ number = count
+ )
+ }
+}
-@Preview
+@Preview(heightDp = 800, showBackground = true)
@Composable
private fun BottlesBalloonPopupPreview() {
BottlesTheme {
Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
BottlesBalloonPopup(
- text = "새로운 보틀이 도착했어요!"
+ text = "보틀을 클릭해 보세요"
)
BottlesBalloonPopupWithButton(
text = "자기소개 작성 후 열어볼 수 있어요",
buttonText = "자기소개 작성하기",
onClick = {}
)
+ BottlesBalloonPopup(
+ text = buildAnnotatedString {
+ withStyle(style = SpanStyle(color = Color(0xFF615EFA))) {
+ append("00")
+ }
+ append("시간후 새로운 보틀이 도착해요")
+ }
+ )
+ BottlesBalloonPopupWithChip(
+ text = "보틀을 클릭해 보세요",
+ count = 3
+ )
}
}
}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Color.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Color.kt
index 1e9aae72..64525c31 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Color.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Color.kt
@@ -113,4 +113,5 @@ internal object IconColors {
internal val iconSecondary = Neutral200
internal val iconDisabled = Neutral200
internal val iconUpdate = Red
+ internal val iconSelected = PrimaryPurple500
}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Shape.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Shape.kt
index 03983726..74bad497 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Shape.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/foundation/Shape.kt
@@ -8,6 +8,7 @@ internal enum class BottlesShapeDefaults(val shape: Shape) {
RADIUS_XS(shape = RoundedCornerShape(8.dp)),
RADIUS_S(shape = RoundedCornerShape(12.dp)),
RADIUS_M(shape = RoundedCornerShape(16.dp)),
+ RADIUS_L(shape = RoundedCornerShape(20.dp)),
RADIUS_XL(shape = RoundedCornerShape(24.dp)),
;
}
\ No newline at end of file
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesColor.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesColor.kt
index 5f0813eb..0fc02fef 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesColor.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesColor.kt
@@ -123,5 +123,6 @@ data class Icon(
val primary: Color = IconColors.iconPrimary,
val secondary: Color = IconColors.iconSecondary,
val disabled: Color = IconColors.iconDisabled,
- val update: Color = IconColors.iconUpdate
+ val update: Color = IconColors.iconUpdate,
+ val selected: Color = IconColors.iconSelected
)
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesShape.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesShape.kt
index 2b9b033e..b164a37c 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesShape.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesShape.kt
@@ -21,6 +21,7 @@ data class BottlesShape(
val extraSmall: Shape,
val small: Shape,
val medium: Shape,
+ val large: Shape,
val extraLarge: Shape,
) {
companion object {
@@ -28,6 +29,7 @@ data class BottlesShape(
extraSmall = BottlesShapeDefaults.RADIUS_XS.shape,
small = BottlesShapeDefaults.RADIUS_S.shape,
medium = BottlesShapeDefaults.RADIUS_M.shape,
+ large = BottlesShapeDefaults.RADIUS_L.shape,
extraLarge = BottlesShapeDefaults.RADIUS_XL.shape,
)
}
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesTypography.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesTypography.kt
index aefa93d2..da4b6ca5 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesTypography.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/theme/BottlesTypography.kt
@@ -39,7 +39,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
letterSpacing = 0.sp,
- lineHeight = 24.sp * 1.3f,
+ lineHeight = 41.6f.sp,
),
title2 =
TextStyle(
@@ -47,7 +47,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
letterSpacing = 0.sp,
- lineHeight = 20.sp * 1.3f,
+ lineHeight = 31.2f.sp,
),
title3 =
TextStyle(
@@ -55,7 +55,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
letterSpacing = 0.sp,
- lineHeight = 20.sp * 1.3f,
+ lineHeight = 26.sp,
),
subTitle1 =
TextStyle(
@@ -63,7 +63,7 @@ data class BottlesTypography(
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
letterSpacing = 0.sp,
- lineHeight = 16.sp * 1.3f,
+ lineHeight = 20.8f.sp,
),
subTitle2 =
TextStyle(
@@ -71,7 +71,7 @@ data class BottlesTypography(
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp,
letterSpacing = 0.sp,
- lineHeight = 14.sp * 1.3f,
+ lineHeight = 18.2f.sp,
),
body =
TextStyle(
@@ -79,7 +79,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 0.sp,
- lineHeight = 14.sp * 1.5f,
+ lineHeight = 21.sp,
),
caption =
TextStyle(
@@ -87,7 +87,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
letterSpacing = 0.sp,
- lineHeight = 12.sp * 1.5f,
+ lineHeight = 18.sp,
),
kakaoLogin =
TextStyle(
@@ -95,7 +95,7 @@ data class BottlesTypography(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
letterSpacing = 0.15.sp,
- lineHeight = 14.sp * 1.4f,
+ lineHeight = 19.6f.sp,
),
)
}
@@ -112,8 +112,8 @@ private fun TypographyPreview() {
) {
Text(
modifier = Modifier.border(1.dp, Color.Blue),
- text = "진심을 담은 보틀로\n서로를 밀도있게 알아가요",
- style = BottlesTheme.typography.title3,
+ text = "아직 보틀을\n찾지 못했어요",
+ style = BottlesTheme.typography.title1,
color = Color.Black,
textAlign = TextAlign.Center
)
diff --git a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/util/BottlesIcons.kt b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/util/BottlesIcons.kt
index 47f1bcb3..eb7050bc 100644
--- a/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/util/BottlesIcons.kt
+++ b/core/design-system/src/main/kotlin/com/team/bottles/core/designsystem/util/BottlesIcons.kt
@@ -18,4 +18,6 @@ object BottlesIcons {
val ic_beach_32 = R.drawable.ic_beach_32
val ic_close_16 = R.drawable.ic_close_16
val ic_spacing_bar_3_15 = R.drawable.ic_spacing_bar_3_15
+ val ic_pencil_12 = R.drawable.ic_pencil_12
+ val ic_warning_24 = R.drawable.ic_warning_24
}
diff --git a/core/design-system/src/main/res/drawable/ic_pencil_12.xml b/core/design-system/src/main/res/drawable/ic_pencil_12.xml
new file mode 100644
index 00000000..7e481c3e
--- /dev/null
+++ b/core/design-system/src/main/res/drawable/ic_pencil_12.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/core/design-system/src/main/res/drawable/ic_warning_24.xml b/core/design-system/src/main/res/drawable/ic_warning_24.xml
new file mode 100644
index 00000000..1e56a5f6
--- /dev/null
+++ b/core/design-system/src/main/res/drawable/ic_warning_24.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/repository/AuthRepository.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/repository/AuthRepository.kt
index 4129d09a..057f6b3e 100644
--- a/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/repository/AuthRepository.kt
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/repository/AuthRepository.kt
@@ -21,4 +21,8 @@ interface AuthRepository {
suspend fun getSavedLocalFcmToken(): String
+ suspend fun getLatestAppVersion(): Int
+
+ suspend fun getRequiredAppVersion(): Int
+
}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetLatestAppVersionUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetLatestAppVersionUseCase.kt
new file mode 100644
index 00000000..6fd17e4e
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetLatestAppVersionUseCase.kt
@@ -0,0 +1,19 @@
+package com.team.bottles.core.domain.auth.usecase
+
+import com.team.bottles.core.domain.auth.repository.AuthRepository
+import javax.inject.Inject
+
+class GetLatestAppVersionUseCaseImpl @Inject constructor(
+ private val authRepository: AuthRepository,
+): GetLatestAppVersionUseCase {
+
+ override suspend fun invoke(): Int =
+ authRepository.getLatestAppVersion()
+
+}
+
+interface GetLatestAppVersionUseCase {
+
+ suspend operator fun invoke(): Int
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetRequiredAppVersionUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetRequiredAppVersionUseCase.kt
new file mode 100644
index 00000000..40f5c898
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/auth/usecase/GetRequiredAppVersionUseCase.kt
@@ -0,0 +1,19 @@
+package com.team.bottles.core.domain.auth.usecase
+
+import com.team.bottles.core.domain.auth.repository.AuthRepository
+import javax.inject.Inject
+
+class GetRequiredAppVersionUseCaseImpl @Inject constructor(
+ private val authRepository: AuthRepository,
+) : GetRequiredAppVersionUseCase {
+
+ override suspend fun invoke(): Int =
+ authRepository.getRequiredAppVersion()
+
+}
+
+interface GetRequiredAppVersionUseCase {
+
+ suspend operator fun invoke(): Int
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/model/Notification.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/model/Notification.kt
new file mode 100644
index 00000000..5e5392b3
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/model/Notification.kt
@@ -0,0 +1,14 @@
+package com.team.bottles.core.domain.user.model
+
+data class Notification(
+ val notificationType: NotificationType,
+ val enabled: Boolean
+)
+
+enum class NotificationType {
+ DAILY_RANDOM,
+ RECEIVE_LIKE,
+ PING_PONG,
+ MARKETING,
+ ;
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/repository/UserRepository.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/repository/UserRepository.kt
index 0eaa25a4..0dc9b08c 100644
--- a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/repository/UserRepository.kt
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/repository/UserRepository.kt
@@ -1,5 +1,7 @@
package com.team.bottles.core.domain.user.repository
+import com.team.bottles.core.domain.user.model.Notification
+
interface UserRepository {
suspend fun reportUser(
@@ -7,4 +9,12 @@ interface UserRepository {
contents: String
)
+ suspend fun loadContacts(): List
+
+ suspend fun updateBlockingContacts(contacts: List)
+
+ suspend fun loadSettingNotifications(): List
+
+ suspend fun updateSettingNotification(notification: Notification)
+
}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetContactsUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetContactsUseCase.kt
new file mode 100644
index 00000000..024db075
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetContactsUseCase.kt
@@ -0,0 +1,19 @@
+package com.team.bottles.core.domain.user.usecase
+
+import com.team.bottles.core.domain.user.repository.UserRepository
+import javax.inject.Inject
+
+class GetContactsUseCaseImpl @Inject constructor(
+ private val userRepository: UserRepository,
+): GetContactsUseCase {
+
+ override suspend fun invoke(): List =
+ userRepository.loadContacts()
+
+}
+
+interface GetContactsUseCase {
+
+ suspend operator fun invoke(): List
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetSettingNotificationsUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetSettingNotificationsUseCase.kt
new file mode 100644
index 00000000..a6081678
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/GetSettingNotificationsUseCase.kt
@@ -0,0 +1,20 @@
+package com.team.bottles.core.domain.user.usecase
+
+import com.team.bottles.core.domain.user.model.Notification
+import com.team.bottles.core.domain.user.repository.UserRepository
+import javax.inject.Inject
+
+class GetSettingNotificationsUseCaseImpl @Inject constructor(
+ private val userRepository: UserRepository,
+) : GetSettingNotificationsUseCase {
+
+ override suspend fun invoke(): List =
+ userRepository.loadSettingNotifications()
+
+}
+
+interface GetSettingNotificationsUseCase {
+
+ suspend operator fun invoke(): List
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateBlockingContactsUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateBlockingContactsUseCase.kt
new file mode 100644
index 00000000..4882c041
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateBlockingContactsUseCase.kt
@@ -0,0 +1,20 @@
+package com.team.bottles.core.domain.user.usecase
+
+import com.team.bottles.core.domain.user.repository.UserRepository
+import javax.inject.Inject
+
+class UpdateBlockingContactsUseCaseImpl @Inject constructor(
+ private val userRepository: UserRepository,
+): UpdateBlockingContactsUseCase {
+
+ override suspend fun invoke(contacts: List) {
+ userRepository.updateBlockingContacts(contacts = contacts)
+ }
+
+}
+
+interface UpdateBlockingContactsUseCase {
+
+ suspend operator fun invoke(contacts: List)
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateSettingNotificationUseCase.kt b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateSettingNotificationUseCase.kt
new file mode 100644
index 00000000..944f2ceb
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/team/bottles/core/domain/user/usecase/UpdateSettingNotificationUseCase.kt
@@ -0,0 +1,21 @@
+package com.team.bottles.core.domain.user.usecase
+
+import com.team.bottles.core.domain.user.model.Notification
+import com.team.bottles.core.domain.user.repository.UserRepository
+import javax.inject.Inject
+
+class UpdateSettingNotificationUseCaseImpl @Inject constructor(
+ private val userRepository: UserRepository,
+): UpdateSettingNotificationUseCase {
+
+ override suspend fun invoke(notification: Notification) {
+ userRepository.updateSettingNotification(notification = notification)
+ }
+
+}
+
+interface UpdateSettingNotificationUseCase {
+
+ suspend operator fun invoke(notification: Notification)
+
+}
\ No newline at end of file
diff --git a/core/local/.gitignore b/core/local/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/local/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/local/build.gradle.kts b/core/local/build.gradle.kts
new file mode 100644
index 00000000..de030653
--- /dev/null
+++ b/core/local/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ id("team.bottles.android.library")
+ id("team.bottles.android.hilt")
+}
+
+android {
+ namespace = "com.team.bottles.local"
+}
+
+dependencies {
+
+}
\ No newline at end of file
diff --git a/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSource.kt b/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSource.kt
new file mode 100644
index 00000000..38ab631b
--- /dev/null
+++ b/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSource.kt
@@ -0,0 +1,7 @@
+package com.team.bottles.local.datasource
+
+interface DeviceDataSource {
+
+ suspend fun getContacts(): List
+
+}
\ No newline at end of file
diff --git a/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSourceImpl.kt b/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSourceImpl.kt
new file mode 100644
index 00000000..6d5e4973
--- /dev/null
+++ b/core/local/src/main/kotlin/com/team/bottles/local/datasource/DeviceDataSourceImpl.kt
@@ -0,0 +1,37 @@
+package com.team.bottles.local.datasource
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.provider.ContactsContract
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class DeviceDataSourceImpl @Inject constructor(
+ @ApplicationContext private val context: Context,
+) : DeviceDataSource {
+
+ override suspend fun getContacts(): List {
+ val contacts = mutableSetOf()
+ val contentResolver: ContentResolver = context.contentResolver
+ val cursor: Cursor? = contentResolver.query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+ null,
+ null,
+ null,
+ null
+ )
+
+ cursor?.use {
+ val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
+
+ while (it.moveToNext()) {
+ val number = it.getString(numberIndex).replace(Regex("[^0-9]"), "")
+ contacts.add(number)
+ }
+ }
+
+ return contacts.toList()
+ }
+
+}
\ No newline at end of file
diff --git a/core/local/src/main/kotlin/com/team/bottles/local/di/LocalDataSourceModule.kt b/core/local/src/main/kotlin/com/team/bottles/local/di/LocalDataSourceModule.kt
new file mode 100644
index 00000000..795d7e1c
--- /dev/null
+++ b/core/local/src/main/kotlin/com/team/bottles/local/di/LocalDataSourceModule.kt
@@ -0,0 +1,17 @@
+package com.team.bottles.local.di
+
+import com.team.bottles.local.datasource.DeviceDataSource
+import com.team.bottles.local.datasource.DeviceDataSourceImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class LocalDataSourceModule {
+
+ @Binds
+ abstract fun bindsDeviceDataSource(dataSourceImpl: DeviceDataSourceImpl): DeviceDataSource
+
+}
\ No newline at end of file
diff --git a/core/navigator/src/main/kotlin/SettingNavigator.kt b/core/navigator/src/main/kotlin/SettingNavigator.kt
new file mode 100644
index 00000000..334b6f4a
--- /dev/null
+++ b/core/navigator/src/main/kotlin/SettingNavigator.kt
@@ -0,0 +1,11 @@
+import kotlinx.serialization.Serializable
+
+sealed interface SettingNavigator {
+
+ @Serializable
+ data object Notification : SettingNavigator
+
+ @Serializable
+ data object Account : SettingNavigator
+
+}
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/api/AuthService.kt b/core/network/src/main/kotlin/com/team/bottles/network/api/AuthService.kt
index d996bed2..0dee2bfa 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/api/AuthService.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/api/AuthService.kt
@@ -8,7 +8,9 @@ import com.team.bottles.network.dto.auth.request.SignUpRequest
import com.team.bottles.network.dto.auth.request.SmsSignInRequest
import com.team.bottles.network.dto.auth.response.KakaoSignInUpResponse
import com.team.bottles.network.dto.auth.response.TokensResponse
+import com.team.bottles.network.dto.auth.response.UpdateAppVersionResponse
import retrofit2.http.Body
+import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
@@ -61,4 +63,7 @@ interface AuthService {
@Body fcmUpdateRequest: FcmUpdateRequest
)
+ @GET("/api/v1/auth/app-version")
+ suspend fun getRequiredMinimumAppVersion(): UpdateAppVersionResponse
+
}
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/api/UserService.kt b/core/network/src/main/kotlin/com/team/bottles/network/api/UserService.kt
index 2e67fae8..ca263de5 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/api/UserService.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/api/UserService.kt
@@ -1,7 +1,11 @@
package com.team.bottles.network.api
+import com.team.bottles.network.dto.auth.request.BlockContactListRequest
+import com.team.bottles.network.dto.user.request.AlimyOnOffRequest
import com.team.bottles.network.dto.user.request.ReportUserRequest
+import com.team.bottles.network.dto.user.response.AlimyResponse
import retrofit2.http.Body
+import retrofit2.http.GET
import retrofit2.http.POST
interface UserService {
@@ -11,4 +15,17 @@ interface UserService {
@Body request: ReportUserRequest
)
+ @POST("/api/v1/user/block/contact-list")
+ suspend fun postBlockedContacts(
+ @Body request: BlockContactListRequest
+ )
+
+ @GET("/api/v1/user/alimy")
+ suspend fun getSettingNotifications(): List
+
+ @POST("/api/v1/user/alimy")
+ suspend fun postSettingNotification(
+ @Body request: AlimyOnOffRequest
+ )
+
}
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSource.kt b/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSource.kt
index 928afcbf..2013fecf 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSource.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSource.kt
@@ -8,6 +8,7 @@ import com.team.bottles.network.dto.auth.request.SignUpRequest
import com.team.bottles.network.dto.auth.request.SmsSignInRequest
import com.team.bottles.network.dto.auth.response.KakaoSignInUpResponse
import com.team.bottles.network.dto.auth.response.TokensResponse
+import com.team.bottles.network.dto.auth.response.UpdateAppVersionResponse
interface AuthDataSource {
@@ -41,4 +42,6 @@ interface AuthDataSource {
request: FcmUpdateRequest
)
+ suspend fun fetchRequiredMinimumAppVersion(): UpdateAppVersionResponse
+
}
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSourceImpl.kt b/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSourceImpl.kt
index 62dc5ee0..54ad6c2a 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSourceImpl.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/datasource/AuthDataSourceImpl.kt
@@ -9,6 +9,7 @@ import com.team.bottles.network.dto.auth.request.SignUpRequest
import com.team.bottles.network.dto.auth.request.SmsSignInRequest
import com.team.bottles.network.dto.auth.response.KakaoSignInUpResponse
import com.team.bottles.network.dto.auth.response.TokensResponse
+import com.team.bottles.network.dto.auth.response.UpdateAppVersionResponse
import javax.inject.Inject
class AuthDataSourceImpl @Inject constructor(
@@ -47,6 +48,9 @@ class AuthDataSourceImpl @Inject constructor(
authService.postUpdatedFcmToken(accessToken = "$TOKEN_TYPE $accessToken", fcmUpdateRequest = request)
}
+ override suspend fun fetchRequiredMinimumAppVersion(): UpdateAppVersionResponse =
+ authService.getRequiredMinimumAppVersion()
+
companion object {
private const val TOKEN_TYPE = "Bearer"
}
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSource.kt b/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSource.kt
index 8e1df838..68785d82 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSource.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSource.kt
@@ -1,9 +1,18 @@
package com.team.bottles.network.datasource
+import com.team.bottles.network.dto.auth.request.BlockContactListRequest
+import com.team.bottles.network.dto.user.request.AlimyOnOffRequest
import com.team.bottles.network.dto.user.request.ReportUserRequest
+import com.team.bottles.network.dto.user.response.AlimyResponse
interface UserDataSource {
suspend fun sendReportContents(request: ReportUserRequest)
+ suspend fun updateWantToBlockContacts(request: BlockContactListRequest)
+
+ suspend fun fetchSettingNotifications(): List
+
+ suspend fun updateSettingNotification(request: AlimyOnOffRequest)
+
}
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSourceImpl.kt b/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSourceImpl.kt
index bd79d645..370e8b33 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSourceImpl.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/datasource/UserDataSourceImpl.kt
@@ -1,7 +1,10 @@
package com.team.bottles.network.datasource
import com.team.bottles.network.api.UserService
+import com.team.bottles.network.dto.auth.request.BlockContactListRequest
+import com.team.bottles.network.dto.user.request.AlimyOnOffRequest
import com.team.bottles.network.dto.user.request.ReportUserRequest
+import com.team.bottles.network.dto.user.response.AlimyResponse
import javax.inject.Inject
class UserDataSourceImpl @Inject constructor(
@@ -12,4 +15,15 @@ class UserDataSourceImpl @Inject constructor(
userService.postReportUser(request = request)
}
+ override suspend fun updateWantToBlockContacts(request: BlockContactListRequest) {
+ userService.postBlockedContacts(request = request)
+ }
+
+ override suspend fun fetchSettingNotifications(): List =
+ userService.getSettingNotifications()
+
+ override suspend fun updateSettingNotification(request: AlimyOnOffRequest) {
+ userService.postSettingNotification(request = request)
+ }
+
}
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/di/NetworkModule.kt b/core/network/src/main/kotlin/com/team/bottles/network/di/NetworkModule.kt
index 278f53c2..e401d74c 100644
--- a/core/network/src/main/kotlin/com/team/bottles/network/di/NetworkModule.kt
+++ b/core/network/src/main/kotlin/com/team/bottles/network/di/NetworkModule.kt
@@ -31,6 +31,7 @@ object NetworkModule {
fun provideJson(): Json = Json {
coerceInputValues = true
prettyPrint = true
+ ignoreUnknownKeys = true
}
@Singleton
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/request/BlockContactListRequest.kt b/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/request/BlockContactListRequest.kt
new file mode 100644
index 00000000..c053c642
--- /dev/null
+++ b/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/request/BlockContactListRequest.kt
@@ -0,0 +1,9 @@
+package com.team.bottles.network.dto.auth.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BlockContactListRequest(
+ @SerialName("blockContacts") val blockContacts: List
+)
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/response/UpdateAppVersionResponse.kt b/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/response/UpdateAppVersionResponse.kt
new file mode 100644
index 00000000..a19e40e0
--- /dev/null
+++ b/core/network/src/main/kotlin/com/team/bottles/network/dto/auth/response/UpdateAppVersionResponse.kt
@@ -0,0 +1,10 @@
+package com.team.bottles.network.dto.auth.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UpdateAppVersionResponse(
+ @SerialName("minimumAndroidVersion") val minimumAndroidVersion: Int?,
+ @SerialName("latestAndroidVersion") val latestAndroidVersion: Int?
+)
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/dto/user/request/AlimyOnOffRequest.kt b/core/network/src/main/kotlin/com/team/bottles/network/dto/user/request/AlimyOnOffRequest.kt
new file mode 100644
index 00000000..07fe8592
--- /dev/null
+++ b/core/network/src/main/kotlin/com/team/bottles/network/dto/user/request/AlimyOnOffRequest.kt
@@ -0,0 +1,11 @@
+package com.team.bottles.network.dto.user.request
+
+import com.team.bottles.network.dto.user.response.AlimyType
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AlimyOnOffRequest(
+ @SerialName("alimyType") val alimyType: AlimyType,
+ @SerialName("enabled") val enabled: Boolean
+)
\ No newline at end of file
diff --git a/core/network/src/main/kotlin/com/team/bottles/network/dto/user/response/AlimyResponse.kt b/core/network/src/main/kotlin/com/team/bottles/network/dto/user/response/AlimyResponse.kt
new file mode 100644
index 00000000..301bb245
--- /dev/null
+++ b/core/network/src/main/kotlin/com/team/bottles/network/dto/user/response/AlimyResponse.kt
@@ -0,0 +1,19 @@
+package com.team.bottles.network.dto.user.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class AlimyResponse(
+ @SerialName("alimyType") val alimyType: AlimyType,
+ @SerialName("enabled") val enabled: Boolean
+)
+
+@Serializable
+enum class AlimyType {
+ @SerialName("DAILY_RANDOM") DAILY_RANDOM,
+ @SerialName("RECEIVE_LIKE") RECEIVE_LIKE,
+ @SerialName("PINGPONG") PING_PONG,
+ @SerialName("MARKETING") MARKETING,
+ ;
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/team/bottles/core/ui/Alert.kt b/core/ui/src/main/kotlin/com/team/bottles/core/ui/Alert.kt
index a4a295de..d0a396d7 100644
--- a/core/ui/src/main/kotlin/com/team/bottles/core/ui/Alert.kt
+++ b/core/ui/src/main/kotlin/com/team/bottles/core/ui/Alert.kt
@@ -1,78 +1,292 @@
package com.team.bottles.core.ui
-import androidx.compose.material3.AlertDialog
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.BasicAlertDialog
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.modifier.noRippleClickable
import com.team.bottles.core.designsystem.theme.BottlesTheme
@Composable
-fun BottlesAlertDialog(
+fun BottlesAlertConfirmDialog(
modifier: Modifier = Modifier,
onClose: () -> Unit,
onConfirm: () -> Unit,
- confirmText: String,
- dismissText: String,
+ confirmButtonText: String,
title: String,
- content: String
+ content: String,
) {
- AlertDialog(
+ BottlesAlertDialog(
modifier = modifier,
- onDismissRequest = onClose,
- confirmButton = {
+ onClose = onClose,
+ title = title,
+ content = content
+ ) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(height = 36.dp)
+ .background(
+ color = BottlesTheme.color.container.enabledSecondary,
+ shape = BottlesTheme.shape.extraSmall
+ )
+ .clip(shape = BottlesTheme.shape.extraSmall)
+ .noRippleClickable(onClick = onConfirm)
+ .padding(horizontal = 12.dp),
+ contentAlignment = Alignment.Center
+ ) {
Text(
- modifier = Modifier
- .noRippleClickable(
- onClick = onConfirm
- ),
- text = confirmText,
- style = BottlesTheme.typography.kakaoLogin,
- color = Color.Red
+ text = confirmButtonText,
+ style = BottlesTheme.typography.body,
+ color = BottlesTheme.color.text.enabledPrimary
)
- },
- dismissButton = {
- Text(
- modifier = Modifier
- .noRippleClickable(
- onClick = onClose
- ),
- text = dismissText,
- style = BottlesTheme.typography.kakaoLogin,
- color = Color.Black
+ }
+ }
+}
+
+@Composable
+fun BottlesAlertDialogLeftConfirmRightDismiss(
+ modifier: Modifier = Modifier,
+ onClose: () -> Unit,
+ onDismiss: () -> Unit,
+ onConfirm: () -> Unit,
+ confirmButtonText: String,
+ dismissButtonText: String,
+ title: String,
+ content: String,
+) {
+ BottlesAlertDialog(
+ modifier = modifier,
+ onClose = onClose,
+ title = title,
+ content = content
+ ) {
+ Row {
+ AlertDialogLeftButton(
+ onClick = onConfirm,
+ text = confirmButtonText,
+ )
+
+ Spacer(modifier = Modifier.width(width = BottlesTheme.spacing.small))
+
+ AlertDialogRightButton(
+ onClick = onDismiss,
+ text = dismissButtonText,
+ )
+ }
+ }
+}
+
+@Composable
+fun BottlesAlertDialogLeftDismissRightConfirm(
+ modifier: Modifier = Modifier,
+ onClose: () -> Unit,
+ onDismiss: () -> Unit,
+ onConfirm: () -> Unit,
+ confirmButtonText: String,
+ dismissButtonText: String,
+ title: String,
+ content: String,
+) {
+ BottlesAlertDialog(
+ modifier = modifier,
+ onClose = onClose,
+ title = title,
+ content = content
+ ) {
+ Row {
+ AlertDialogLeftButton(
+ onClick = onDismiss,
+ text = dismissButtonText,
+ )
+
+ Spacer(modifier = Modifier.width(width = BottlesTheme.spacing.small))
+
+ AlertDialogRightButton(
+ onClick = onConfirm,
+ text = confirmButtonText,
)
- },
- title = {
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BottlesAlertDialog(
+ modifier: Modifier = Modifier,
+ onClose: () -> Unit,
+ title: String,
+ content: String,
+ buttons: @Composable () -> Unit
+) {
+ BasicAlertDialog(
+ modifier = modifier,
+ onDismissRequest = onClose
+ ) {
+ Column(
+ modifier = Modifier
+ .background(
+ color = Color.White,
+ shape = BottlesTheme.shape.large
+ )
+ .padding(
+ top = BottlesTheme.spacing.large,
+ bottom = BottlesTheme.spacing.medium,
+ start = BottlesTheme.spacing.medium,
+ end = BottlesTheme.spacing.medium,
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_warning_24),
+ contentDescription = null,
+ tint = Color.Unspecified
+ )
+
+ Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.extraSmall))
+
Text(
text = title,
style = BottlesTheme.typography.subTitle1,
- color = Color.Black
+ color = BottlesTheme.color.text.primary
)
- },
- text = {
+
+ Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.doubleExtraSmall))
+
Text(
text = content,
+ textAlign = TextAlign.Center,
style = BottlesTheme.typography.body,
- color = Color.Gray
+ color = BottlesTheme.color.text.secondary
)
+
+ Spacer(
+ modifier = Modifier.height(
+ height = BottlesTheme.spacing.small + BottlesTheme.spacing.medium
+ )
+ )
+
+ buttons()
}
- )
+ }
+}
+@Composable
+private fun RowScope.AlertDialogRightButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ text: String,
+) {
+ Box(
+ modifier = modifier
+ .height(height = 36.dp)
+ .weight(1f)
+ .background(
+ color = BottlesTheme.color.container.enabledSecondary,
+ shape = BottlesTheme.shape.extraSmall
+ )
+ .clip(shape = BottlesTheme.shape.extraSmall)
+ .noRippleClickable(onClick = onClick)
+ .padding(horizontal = 12.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ style = BottlesTheme.typography.body,
+ color = BottlesTheme.color.text.enabledPrimary
+ )
+ }
+}
+
+@Composable
+private fun RowScope.AlertDialogLeftButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ text: String,
+) {
+ Box(
+ modifier = modifier
+ .height(height = 36.dp)
+ .weight(1f)
+ .background(
+ color = BottlesTheme.color.container.disabledSecondary,
+ shape = BottlesTheme.shape.extraSmall
+ )
+ .clip(shape = BottlesTheme.shape.extraSmall)
+ .noRippleClickable(onClick = onClick)
+ .padding(horizontal = 12.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ style = BottlesTheme.typography.body,
+ color = BottlesTheme.color.text.enabledPrimary
+ )
+ }
+}
+
+@Preview(widthDp = 360, heightDp = 640, showBackground = true)
+@Composable
+private fun BottlesAlertDialogLeftDismissRightConfirmPreview() {
+ BottlesTheme {
+ BottlesAlertDialogLeftDismissRightConfirm(
+ onClose = {},
+ onConfirm = {},
+ onDismiss = {},
+ confirmButtonText = "차단하기",
+ dismissButtonText = "취소하기",
+ title = "연락처 차단",
+ content = "주소록에 있는 000개의\n전화번호를 차단할까요?",
+ )
+ }
+}
+
+@Preview(widthDp = 360, heightDp = 640, showBackground = true)
+@Composable
+private fun BottlesAlertDialogLeftConfirmRightDismissPreview() {
+ BottlesTheme {
+ BottlesAlertDialogLeftConfirmRightDismiss(
+ onClose = {},
+ onConfirm = {},
+ onDismiss = {},
+ confirmButtonText = "로그아웃하기",
+ dismissButtonText = "취소하기",
+ title = "로그아웃",
+ content = "정말 로그아웃하시겠어요?",
+ )
+ }
}
-@Preview
+@Preview(widthDp = 360, heightDp = 640, showBackground = true)
@Composable
-private fun BottlesAlterDialogPreview() {
+private fun BottlesAlertConfirmDialogPreview() {
BottlesTheme {
- BottlesAlertDialog(
+ BottlesAlertConfirmDialog(
onClose = {},
onConfirm = {},
- confirmText = "탈퇴하기",
- dismissText = "취소하기",
- title = "탈퇴하기",
- content = "탈퇴 시 계정 복구가 어려워요.\n정말 탈퇴하시겠어요?"
+ confirmButtonText = "업데이트 하기",
+ title = "업데이트 안내",
+ content = "최적의 사용 환경을 위해\n최신 버전의 앱으로 업데이트 해주세요",
)
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/team/bottles/core/ui/CardProfile.kt b/core/ui/src/main/kotlin/com/team/bottles/core/ui/CardProfile.kt
index 3ae96cfd..5415d53e 100644
--- a/core/ui/src/main/kotlin/com/team/bottles/core/ui/CardProfile.kt
+++ b/core/ui/src/main/kotlin/com/team/bottles/core/ui/CardProfile.kt
@@ -2,45 +2,29 @@
package com.team.bottles.core.ui
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.team.bottles.core.designsystem.components.buttons.BottlesOutLinedButton
import com.team.bottles.core.designsystem.components.buttons.OutlinedButtonType
+import com.team.bottles.core.designsystem.components.cards.BottlesCard
import com.team.bottles.core.designsystem.theme.BottlesTheme
import com.team.bottles.core.ui.model.UserKeyPoint
@Composable
fun CardProfile(
+ modifier: Modifier = Modifier,
keyPoints: List,
) {
- Column(
- modifier = Modifier
- .background(
- color = BottlesTheme.color.container.primary,
- shape = BottlesTheme.shape.extraLarge
- )
- .clip(shape = BottlesTheme.shape.extraLarge)
- .border(
- width = 1.dp,
- shape = BottlesTheme.shape.extraLarge,
- color = BottlesTheme.color.border.primary
- )
- .padding(
- vertical = BottlesTheme.spacing.extraLarge,
- horizontal = BottlesTheme.spacing.small
- ),
+ BottlesCard(
+ modifier = modifier,
verticalArrangement = Arrangement.spacedBy(
space = BottlesTheme.spacing.extraLarge
)
diff --git a/core/ui/src/main/kotlin/com/team/bottles/core/ui/model/AlertType.kt b/core/ui/src/main/kotlin/com/team/bottles/core/ui/model/AlertType.kt
index 641e93fa..68069dd8 100644
--- a/core/ui/src/main/kotlin/com/team/bottles/core/ui/model/AlertType.kt
+++ b/core/ui/src/main/kotlin/com/team/bottles/core/ui/model/AlertType.kt
@@ -17,24 +17,34 @@ enum class AlertType(
override val dismissText: String,
override val title: String,
override val content: String
-): AlertInfo {
+) : AlertInfo {
LOG_OUT(
title = "로그아웃",
content = "정말 로그아웃 하시겠어요?",
- confirmText = "로그아웃",
+ confirmText = "로그아웃하기",
dismissText = "취소하기"
),
DELETE_USER(
title = "탈퇴하기",
- content = "탈퇴 시 계정 복구가 어려워요.\n정말 탈퇴하시겠어요?",
+ content = "탈퇴 시 계정 복구가 어려워요.\n" +
+ "정말 탈퇴하시겠어요?",
confirmText = "탈퇴하기",
dismissText = "취소하기"
),
STOP_PING_PONG(
title = "중단하기",
- content = "중단 시 모든 핑퐁 내용이 사라져요.\n정말 중단하시겠어요?",
+ content = "중단 시 모든 핑퐁 내용이 사라져요.\n" +
+ "정말 중단하시겠어요?",
confirmText = "중단하기",
dismissText = "계속하기"
),
+ USER_REPORT(
+ title = "신고하기",
+ content = "접수 후 취소할 수 없으며\n" +
+ "해당 사용자는 차단돼요.\n" +
+ "정말 신고하시겠어요?",
+ confirmText = "신고하기",
+ dismissText = "계속하기"
+ )
;
}
diff --git a/feat/mypage/build.gradle.kts b/feat/mypage/build.gradle.kts
index f117b579..961f5472 100644
--- a/feat/mypage/build.gradle.kts
+++ b/feat/mypage/build.gradle.kts
@@ -1,5 +1,3 @@
-import org.jetbrains.kotlin.konan.properties.Properties
-
plugins {
id("team.bottles.android.library.compose")
id("team.bottles.android.library")
@@ -11,24 +9,9 @@ plugins {
android {
namespace = "com.team.bottles.feat.mypage"
- buildTypes {
- val debugUrl = "BOTTLES_MY_PAGE_URL"
- val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) }
-
- getByName("release") {
- buildConfigField(
- "String",
- debugUrl, // TODO : 릴리즈 용 URL 생성 가능성 있음
- properties.getProperty(debugUrl) // // TODO : 릴리즈 용 URL 생성 가능성 있음
- )
- }
- getByName("debug") {
- buildConfigField(
- "String",
- debugUrl,
- properties.getProperty(debugUrl)
- )
- }
+ defaultConfig {
+ buildConfigField("String", "VERSION_NAME", "\"${libs.versions.versionName.get()}\"")
+ buildConfigField("Integer", "VERSION_CODE", libs.versions.versionCode.get())
}
}
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageBridge.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageBridge.kt
deleted file mode 100644
index 9b557d07..00000000
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageBridge.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.team.bottles.feat.mypage
-
-import android.webkit.JavascriptInterface
-
-internal class MyPageBridge(private val onAction: (MyPageWebAction) -> Unit) : MyPageBridgeListener {
-
- @JavascriptInterface
- override fun logout() {
- onAction(MyPageWebAction.OnLogOut)
- }
-
- @JavascriptInterface
- override fun deleteUser() {
- onAction(MyPageWebAction.OnDeleteUser)
- }
-
- companion object {
- const val NAME = "Native"
- }
-
-}
-
-sealed interface MyPageWebAction {
-
- data object OnLogOut : MyPageWebAction
-
- data object OnDeleteUser : MyPageWebAction
-
-}
-
-interface MyPageBridgeListener {
-
- fun logout()
-
- fun deleteUser()
-
-}
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageRoute.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageRoute.kt
index a1b42c25..c53b3dc3 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageRoute.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageRoute.kt
@@ -1,8 +1,19 @@
package com.team.bottles.feat.mypage
+import android.Manifest
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.provider.Settings
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.team.bottles.feat.mypage.mvi.MyPageSideEffect
@@ -10,14 +21,75 @@ import com.team.bottles.feat.mypage.mvi.MyPageSideEffect
@Composable
internal fun MyPageRoute(
viewModel: MyPageViewModel = hiltViewModel(),
- navigateToLoginEndPoint: () -> Unit
+ navigateToEditProfile: () -> Unit,
+ navigateToSettingNotification: () -> Unit,
+ navigateToSettingAccountManagement: () -> Unit,
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+ val permissionLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission(),
+ onResult = { isGranted ->
+ if (isGranted) {
+ viewModel.fetchContacts()
+ } else {
+ Toast.makeText(context,"연락처 권한을 동의 해야합니다.", Toast.LENGTH_SHORT).show()
+
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
+ data = Uri.fromParts("package", context.packageName, null)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ context.startActivity(intent)
+ }
+ }
+ )
+
+ LaunchedEffect(Unit) {
+ viewModel.checkAppVersion()
+ }
LaunchedEffect(Unit) {
viewModel.sideEffect.collect { sideEffect ->
when (sideEffect) {
- is MyPageSideEffect.NavigateToLoginEndPoint -> navigateToLoginEndPoint()
+ is MyPageSideEffect.NavigateToEditProfile -> navigateToEditProfile()
+ is MyPageSideEffect.NavigateToSettingNotification -> navigateToSettingNotification()
+ is MyPageSideEffect.NavigateToSettingAccountManagement -> navigateToSettingAccountManagement()
+ is MyPageSideEffect.NavigateToPolicyNotion -> {
+ val intent =
+ Intent(Intent.ACTION_VIEW, Uri.parse("https://spiral-ogre-a4d.notion.site/abb2fd284516408e8c2fc267d07c6421")) // 개인 정보 처리 방침 URL
+ context.startActivity(intent)
+ }
+ is MyPageSideEffect.NavigateToTermsOfUseNotion -> {
+ val intent =
+ Intent(Intent.ACTION_VIEW, Uri.parse("https://spiral-ogre-a4d.notion.site/240724-e3676639ea864147bb293cfcda40d99f")) // 이용약관 URL
+ context.startActivity(intent)
+ }
+ is MyPageSideEffect.NavigateToKakaoBusinessChannel -> {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse("kakaoplus://plusfriend/friend/_hDIQG")) // 카톡으로 카카오 플러스 열기
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://pf.kakao.com/_hDIQG")) // URL로 카카오 플러스 열기
+ context.startActivity(webIntent)
+ }
+ }
+ is MyPageSideEffect.CheckContactPermission -> {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ permissionLauncher.launch(Manifest.permission.READ_CONTACTS)
+ } else {
+ viewModel.fetchContacts()
+ }
+ }
+ is MyPageSideEffect.NavigateToPlayStore -> {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.team.bottles&hl=ko"))
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.team.bottles&hl=ko"))
+ context.startActivity(webIntent)
+ }
+ }
}
}
}
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageScreen.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageScreen.kt
index 6033acdc..5b4fc9ad 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageScreen.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageScreen.kt
@@ -1,68 +1,79 @@
package com.team.bottles.feat.mypage
-import android.annotation.SuppressLint
-import android.webkit.WebView
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.components.bars.BottlesTopBar
+import com.team.bottles.core.designsystem.components.etc.BottlesUserInfo
import com.team.bottles.core.designsystem.theme.BottlesTheme
-import com.team.bottles.core.ui.BottlesAlertDialog
-import com.team.bottles.core.ui.BottlesWebView
-import com.team.bottles.core.ui.model.AlertType
+import com.team.bottles.core.ui.BottlesAlertDialogLeftDismissRightConfirm
+import com.team.bottles.feat.mypage.components.SettingList
import com.team.bottles.feat.mypage.mvi.MyPageIntent
import com.team.bottles.feat.mypage.mvi.MyPageUiState
-@SuppressLint("JavascriptInterface")
@Composable
internal fun MyPageScreen(
uiState: MyPageUiState,
onIntent: (MyPageIntent) -> Unit
) {
- val context = LocalContext.current
- val webView = remember {
- WebView(context).apply {
- addJavascriptInterface(
- MyPageBridge { webAction ->
- when (webAction) {
- is MyPageWebAction.OnDeleteUser -> onIntent(MyPageIntent.ClickWebDeleteUserButton)
- is MyPageWebAction.OnLogOut -> onIntent(MyPageIntent.ClickWebLogOutButton)
- }
- },
- MyPageBridge.NAME
- )
- }
+ val scrollState = rememberScrollState()
+
+ if (uiState.showDialog) {
+ BottlesAlertDialogLeftDismissRightConfirm(
+ onClose = { onIntent(MyPageIntent.CloseDialog) },
+ onDismiss = { onIntent(MyPageIntent.CloseDialog) },
+ onConfirm = { onIntent(MyPageIntent.ClickConfirmButton) },
+ confirmButtonText = "차단하기",
+ dismissButtonText = "취소하기",
+ title = "연락처 차단",
+ content = "주소록에 있는 ${uiState.inDeviceContacts.size}개의\n"
+ + "전화번호를 차단할까요?"
+ )
}
- Box(
- modifier = Modifier.fillMaxSize()
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(state = scrollState)
) {
- if (uiState.showDialog) {
- BottlesAlertDialog(
- onClose = { onIntent(MyPageIntent.ClickCancel) },
- onConfirm = {
- if (uiState.alertType == AlertType.LOG_OUT) {
- onIntent(MyPageIntent.ClickDialogLogOutButton)
- } else if (uiState.alertType == AlertType.DELETE_USER) {
- onIntent(MyPageIntent.ClickDialogDeleteUserButton)
- }
- },
- confirmText = uiState.alertType.confirmText,
- dismissText = uiState.alertType.dismissText,
- title = uiState.alertType.title,
- content = uiState.alertType.content
- )
- }
+ BottlesTopBar()
+
+ Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.doubleExtraLarge))
+
+ BottlesUserInfo(
+ modifier = Modifier.padding(horizontal = 32.dp),
+ imageUrl = uiState.imageUrl,
+ userName = uiState.userName,
+ userAge = uiState.userAge,
+ isBlur = false
+ )
+
+ Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.doubleExtraLarge))
+
+ SettingList(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ onClickEditProfile = { onIntent(MyPageIntent.ClickEditProfile) },
+ onClickUpdateBlockContact = { onIntent(MyPageIntent.ClickUpdateBlockContact) },
+ onClickSettingNotification = { onIntent(MyPageIntent.ClickSettingNotification) },
+ onClickAccountManagement = { onIntent(MyPageIntent.ClickAccountManagement) },
+ onClickUpdateAppVersion = { onIntent(MyPageIntent.ClickUpdateAppVersion) },
+ onClickAsk = { onIntent(MyPageIntent.ClickAsk) },
+ onClickTermsOfUse = { onIntent(MyPageIntent.ClickTermsOfUse) },
+ onClickPolicy = { onIntent(MyPageIntent.ClickPolicy) },
+ blockedUserValue = uiState.blockedUserValue,
+ appVersion = uiState.appVersionName,
+ canUpdateAppVersion = uiState.canUpdateAppVersion
+ )
- if (uiState.token.accessToken.isNotEmpty() && uiState.token.refreshToken.isNotEmpty()) {
- BottlesWebView(
- url = BuildConfig.BOTTLES_MY_PAGE_URL + "?accessToken=${uiState.token.accessToken}&refreshToken=${uiState.token.refreshToken}",
- webView = webView
- )
- }
+ Spacer(modifier = Modifier.height(height = 24.dp))
}
}
@@ -71,7 +82,11 @@ internal fun MyPageScreen(
private fun MyPageScreenPreview() {
BottlesTheme {
MyPageScreen(
- uiState = MyPageUiState(showDialog = true),
+ uiState = MyPageUiState(
+ userName = "뇽뇽이",
+ userAge = 15,
+ appVersionName = "1.0.0"
+ ),
onIntent = {}
)
}
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageViewModel.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageViewModel.kt
index 6f116b44..08dd4a17 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageViewModel.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/MyPageViewModel.kt
@@ -2,11 +2,10 @@ package com.team.bottles.feat.mypage
import androidx.lifecycle.SavedStateHandle
import com.team.bottles.core.common.BaseViewModel
-import com.team.bottles.core.domain.auth.model.Token
-import com.team.bottles.core.domain.auth.usecase.DeleteUserUseCase
-import com.team.bottles.core.domain.auth.usecase.LogOutUseCase
-import com.team.bottles.core.domain.auth.usecase.WebViewConnectUseCase
-import com.team.bottles.core.ui.model.AlertType
+import com.team.bottles.core.domain.auth.usecase.GetLatestAppVersionUseCase
+import com.team.bottles.core.domain.profile.usecase.GetUserProfileUseCase
+import com.team.bottles.core.domain.user.usecase.GetContactsUseCase
+import com.team.bottles.core.domain.user.usecase.UpdateBlockingContactsUseCase
import com.team.bottles.feat.mypage.mvi.MyPageIntent
import com.team.bottles.feat.mypage.mvi.MyPageSideEffect
import com.team.bottles.feat.mypage.mvi.MyPageUiState
@@ -15,16 +14,23 @@ import javax.inject.Inject
@HiltViewModel
class MyPageViewModel @Inject constructor(
- private val logOutUseCase: LogOutUseCase,
- private val deleteUserUseCase: DeleteUserUseCase,
- private val webViewConnectUseCase: WebViewConnectUseCase,
+ private val getContactsUseCase: GetContactsUseCase,
+ private val getLatestAppVersionUseCase: GetLatestAppVersionUseCase,
+ private val updateBlockingContactsUseCase: UpdateBlockingContactsUseCase,
+ private val getUserProfileUseCase: GetUserProfileUseCase,
savedStateHandle: SavedStateHandle
) : BaseViewModel(
savedStateHandle
) {
init {
- initialWebConnect()
+ launch {
+ val profile = getUserProfileUseCase()
+ val userImageUrl = profile.imageUrl
+ val userName = profile.userName
+ val userAge = profile.age
+ reduce { copy(imageUrl = userImageUrl, userName = userName, userAge = userAge) }
+ }
}
override fun createInitialState(savedStateHandle: SavedStateHandle): MyPageUiState =
@@ -32,11 +38,16 @@ class MyPageViewModel @Inject constructor(
override suspend fun handleIntent(intent: MyPageIntent) {
when (intent) {
- is MyPageIntent.ClickWebLogOutButton -> reduce { copy(alertType = AlertType.LOG_OUT, showDialog = true) }
- is MyPageIntent.ClickWebDeleteUserButton -> reduce { copy(alertType = AlertType.DELETE_USER, showDialog = true) }
- is MyPageIntent.ClickCancel -> reduce { copy(showDialog = false) }
- is MyPageIntent.ClickDialogLogOutButton -> logOut()
- is MyPageIntent.ClickDialogDeleteUserButton -> deleteUser()
+ is MyPageIntent.ClickEditProfile -> navigateToEditProfile()
+ is MyPageIntent.ClickUpdateBlockContact -> checkContactPermission()
+ is MyPageIntent.ClickSettingNotification -> navigateToSettingNotification()
+ is MyPageIntent.ClickAccountManagement -> navigateToSettingAccountManagement()
+ is MyPageIntent.ClickUpdateAppVersion -> navigateToPlayStore()
+ is MyPageIntent.ClickAsk -> navigateToKakaoBusinessChannel()
+ is MyPageIntent.ClickTermsOfUse -> navigateToTermsOfUseNotion()
+ is MyPageIntent.ClickPolicy -> navigateToPolicyNotion()
+ is MyPageIntent.ClickConfirmButton -> updateBlockContact()
+ is MyPageIntent.CloseDialog -> closeDialog()
}
}
@@ -44,35 +55,71 @@ class MyPageViewModel @Inject constructor(
TODO("Not yet implemented")
}
- private fun logOut() {
+ private fun navigateToEditProfile() {
+ postSideEffect(MyPageSideEffect.NavigateToEditProfile)
+ }
+
+ private fun navigateToSettingNotification() {
+ postSideEffect(MyPageSideEffect.NavigateToSettingNotification)
+ }
+
+ private fun navigateToSettingAccountManagement() {
+ postSideEffect(MyPageSideEffect.NavigateToSettingAccountManagement)
+ }
+
+ private fun navigateToKakaoBusinessChannel() {
+ postSideEffect(MyPageSideEffect.NavigateToKakaoBusinessChannel)
+ }
+
+ private fun navigateToTermsOfUseNotion() {
+ postSideEffect(MyPageSideEffect.NavigateToTermsOfUseNotion)
+ }
+
+ private fun navigateToPolicyNotion() {
+ postSideEffect(MyPageSideEffect.NavigateToPolicyNotion)
+ }
+
+ private fun navigateToPlayStore() {
+ postSideEffect(MyPageSideEffect.NavigateToPlayStore)
+ }
+
+ private fun checkContactPermission() {
+ postSideEffect(MyPageSideEffect.CheckContactPermission)
+ }
+
+ private fun showBlockContactDialog() {
+ reduce { copy(showDialog = true) }
+ }
+
+ private fun closeDialog() {
+ reduce { copy(showDialog = false) }
+ }
+
+ private fun updateBlockContact() {
launch {
- logOutUseCase()
+ updateBlockingContactsUseCase(contacts = currentState.inDeviceContacts)
+ // TODO : 차단한 연락처 갯수 얻는 API 호출
reduce { copy(showDialog = false) }
- postSideEffect(MyPageSideEffect.NavigateToLoginEndPoint)
}
}
- private fun deleteUser() {
+ fun checkAppVersion() {
launch {
- deleteUserUseCase()
- reduce { copy(showDialog = false) }
- postSideEffect(MyPageSideEffect.NavigateToLoginEndPoint)
+ val latestAppVersionCode = getLatestAppVersionUseCase()
+ val currentAppVersion = currentState.appVersionCode
+
+ if (latestAppVersionCode > currentAppVersion) {
+ reduce { copy(canUpdateAppVersion = true) }
+ }
}
}
- private fun initialWebConnect() {
+ fun fetchContacts() {
launch {
- webViewConnectUseCase.getLocalToken().run {
- reduce {
- copy(
- token = Token(
- accessToken = accessToken,
- refreshToken = refreshToken
- )
- )
- }
- }
+ val contacts = getContactsUseCase()
+ reduce { copy(inDeviceContacts = contacts) }
+ showBlockContactDialog()
}
}
-}
\ No newline at end of file
+}
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/components/SettingList.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/components/SettingList.kt
new file mode 100644
index 00000000..ebaf950d
--- /dev/null
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/components/SettingList.kt
@@ -0,0 +1,129 @@
+package com.team.bottles.feat.mypage.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.components.cards.BottlesCard
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItem
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithArrow
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithButton
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+internal fun SettingList(
+ modifier: Modifier = Modifier,
+ onClickEditProfile: () -> Unit,
+ onClickUpdateBlockContact: () -> Unit,
+ onClickSettingNotification: () -> Unit,
+ onClickAccountManagement: () -> Unit,
+ onClickUpdateAppVersion: () -> Unit,
+ onClickAsk: () -> Unit,
+ onClickTermsOfUse: () -> Unit,
+ onClickPolicy: () -> Unit,
+ blockedUserValue: Int,
+ appVersion: String,
+ canUpdateAppVersion: Boolean,
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.small
+ )
+ ) {
+ /*BottlesCard {
+ BottlesSettingItemWithArrow(
+ title = "프로필 수정",
+ onClickItem = onClickEditProfile
+ )
+ }*/ // TODO : 웹뷰 작업 완료시 기능 추가
+
+ BottlesCard(
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.large
+ )
+ ) {
+ BottlesSettingItemWithButton(
+ title = "연락처 차단",
+ subTitle = "기기 내 모든 연락처 차단", // TODO : 연락처 속 ${blockedUserValue}명을 차단햇어요
+ onClickButton = onClickUpdateBlockContact,
+ buttonText = "업데이트"
+ )
+
+ BottlesSettingItemWithArrow(
+ title = "알림 설정",
+ onClickItem = onClickSettingNotification
+ )
+
+ BottlesSettingItemWithArrow(
+ title = "계정 관리",
+ onClickItem = onClickAccountManagement
+ )
+
+ HorizontalDivider(
+ modifier = Modifier.fillMaxWidth(),
+ thickness = 1.dp,
+ color = BottlesTheme.color.border.secondary
+ )
+
+ if (canUpdateAppVersion) {
+ BottlesSettingItemWithButton(
+ title = "앱 버전",
+ subTitle = appVersion,
+ onClickButton = onClickUpdateAppVersion,
+ buttonText = "업데이트"
+ )
+ } else {
+ BottlesSettingItem(
+ title = "앱 버전",
+ subTitle = appVersion
+ )
+ }
+
+ BottlesSettingItemWithArrow(
+ title = "1:1 문의",
+ onClickItem = onClickAsk
+ )
+
+ HorizontalDivider(
+ modifier = Modifier.fillMaxWidth(),
+ thickness = 1.dp,
+ color = BottlesTheme.color.border.secondary
+ )
+
+ BottlesSettingItemWithArrow(
+ title = "보틀 이용약관",
+ onClickItem = onClickTermsOfUse
+ )
+
+ BottlesSettingItemWithArrow(
+ title = "개인정보처리방침",
+ onClickItem = onClickPolicy
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun SettingListPreview() {
+ BottlesTheme {
+ SettingList(
+ onClickEditProfile = { /*TODO*/ },
+ onClickUpdateBlockContact = { /*TODO*/ },
+ onClickSettingNotification = { /*TODO*/ },
+ onClickAccountManagement = { /*TODO*/ },
+ onClickUpdateAppVersion = { /*TODO*/ },
+ onClickAsk = { /*TODO*/ },
+ onClickTermsOfUse = { /*TODO*/ },
+ onClickPolicy = { /*TODO*/ },
+ blockedUserValue = 5,
+ appVersion = "1.0.0",
+ canUpdateAppVersion = false
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageIntent.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageIntent.kt
index 128c6b6b..a2950f4a 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageIntent.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageIntent.kt
@@ -4,14 +4,24 @@ import com.team.bottles.core.common.UiIntent
sealed interface MyPageIntent : UiIntent {
- data object ClickWebLogOutButton : MyPageIntent
+ data object ClickEditProfile : MyPageIntent
- data object ClickWebDeleteUserButton : MyPageIntent
+ data object ClickUpdateBlockContact : MyPageIntent
- data object ClickDialogDeleteUserButton : MyPageIntent
+ data object ClickSettingNotification : MyPageIntent
- data object ClickDialogLogOutButton : MyPageIntent
+ data object ClickAccountManagement : MyPageIntent
- data object ClickCancel : MyPageIntent
+ data object ClickUpdateAppVersion : MyPageIntent
+
+ data object ClickAsk : MyPageIntent
+
+ data object ClickTermsOfUse : MyPageIntent
+
+ data object ClickPolicy : MyPageIntent
+
+ data object ClickConfirmButton : MyPageIntent
+
+ data object CloseDialog : MyPageIntent
}
\ No newline at end of file
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageSideEffect.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageSideEffect.kt
index ae6dbf73..aa7b9e44 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageSideEffect.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageSideEffect.kt
@@ -4,6 +4,20 @@ import com.team.bottles.core.common.UiSideEffect
sealed interface MyPageSideEffect : UiSideEffect {
- data object NavigateToLoginEndPoint : MyPageSideEffect
+ data object NavigateToEditProfile : MyPageSideEffect
+
+ data object NavigateToSettingNotification : MyPageSideEffect
+
+ data object NavigateToSettingAccountManagement : MyPageSideEffect
+
+ data object NavigateToKakaoBusinessChannel : MyPageSideEffect
+
+ data object NavigateToTermsOfUseNotion : MyPageSideEffect
+
+ data object NavigateToPolicyNotion : MyPageSideEffect
+
+ data object CheckContactPermission : MyPageSideEffect
+
+ data object NavigateToPlayStore : MyPageSideEffect
}
\ No newline at end of file
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageUiState.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageUiState.kt
index 91124d72..6a0d4f6a 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageUiState.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/mvi/MyPageUiState.kt
@@ -1,11 +1,16 @@
package com.team.bottles.feat.mypage.mvi
import com.team.bottles.core.common.UiState
-import com.team.bottles.core.domain.auth.model.Token
-import com.team.bottles.core.ui.model.AlertType
+import com.team.bottles.feat.mypage.BuildConfig
data class MyPageUiState(
- val token: Token = Token(),
- val alertType: AlertType = AlertType.LOG_OUT,
val showDialog: Boolean = false,
+ val imageUrl: String = "",
+ val userName: String = "",
+ val userAge: Int = 0,
+ val blockedUserValue: Int = 0,
+ val appVersionName: String = BuildConfig.VERSION_NAME,
+ val appVersionCode: Int = BuildConfig.VERSION_CODE,
+ val canUpdateAppVersion: Boolean = false,
+ val inDeviceContacts: List = emptyList(),
) : UiState
diff --git a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/navigation/MyPageNavigation.kt b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/navigation/MyPageNavigation.kt
index 8b453c01..2d44222e 100644
--- a/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/navigation/MyPageNavigation.kt
+++ b/feat/mypage/src/main/kotlin/com/team/bottles/feat/mypage/navigation/MyPageNavigation.kt
@@ -6,9 +6,15 @@ import androidx.navigation.compose.composable
import com.team.bottles.feat.mypage.MyPageRoute
fun NavGraphBuilder.myPageScreen(
- navigateToLoginEndPoint: () -> Unit
+ navigateToEditProfile: () -> Unit,
+ navigateToSettingNotification: () -> Unit,
+ navigateToSettingAccountManagement: () -> Unit,
) {
composable {
- MyPageRoute(navigateToLoginEndPoint = navigateToLoginEndPoint)
+ MyPageRoute(
+ navigateToEditProfile = navigateToEditProfile,
+ navigateToSettingNotification = navigateToSettingNotification,
+ navigateToSettingAccountManagement = navigateToSettingAccountManagement
+ )
}
}
diff --git a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingScreen.kt b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingScreen.kt
index 31df87a2..77c603ab 100644
--- a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingScreen.kt
+++ b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingScreen.kt
@@ -11,8 +11,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.team.bottles.core.designsystem.R
@@ -34,8 +40,10 @@ internal fun OnboardingScreen(
uiState: OnboardingUiState,
onIntent: (OnboardingIntent) -> Unit
) {
+ var bottomBarHeight by remember { mutableIntStateOf(0) }
+
Scaffold(
- containerColor = BottlesTheme.color.background.primary,
+ containerColor = Color.Transparent,
modifier = Modifier.fillMaxSize(),
topBar = {
BottlesTopBar(
@@ -52,8 +60,27 @@ internal fun OnboardingScreen(
}
)
},
+ bottomBar = {
+ BottlesBottomBar(
+ modifier = Modifier
+ .onGloballyPositioned {
+ bottomBarHeight = it.size.height
+ },
+ text = if (uiState.currentPage.ordinal + 2 != uiState.maxPage) "다음"
+ else "확인",
+ onClick = { onIntent(OnboardingIntent.ClickNextButton) },
+ enabled = true,
+ isDebounce = false
+ )
+ }
) { contentPadding ->
- Box(modifier = Modifier.padding(contentPadding)) {
+ Box(
+ modifier = Modifier
+ .background(color = BottlesTheme.color.background.primary)
+ .padding(
+ top = contentPadding.calculateTopPadding(),
+ )
+ ) {
Column(
modifier = Modifier
.fillMaxSize()
@@ -63,7 +90,7 @@ internal fun OnboardingScreen(
Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.extraLarge))
StepTitle(
- currentPage = uiState.currentPage.ordinal + 2,
+ currentPage = uiState.currentPage.ordinal + 1,
maxPage = uiState.maxPage,
titleText = uiState.currentPage.title
)
@@ -77,16 +104,8 @@ internal fun OnboardingScreen(
OnboardingPage.FOUR -> StepFour()
}
- Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.extraLarge))
+ Spacer(modifier = Modifier.height(height = (bottomBarHeight / 2).dp))
}
-
- BottlesBottomBar(
- modifier = Modifier.align(Alignment.BottomCenter),
- text = if (uiState.currentPage.ordinal + 2 != uiState.maxPage) "다음"
- else "확인",
- onClick = { onIntent(OnboardingIntent.ClickNextButton) },
- enabled = true
- )
}
}
}
diff --git a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingViewModel.kt b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingViewModel.kt
index 06eed36d..f1954964 100644
--- a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingViewModel.kt
+++ b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/OnboardingViewModel.kt
@@ -30,7 +30,7 @@ class OnboardingViewModel @Inject constructor(
}
private fun nextPage() {
- if (currentState.currentPage.ordinal + 3 > currentState.maxPage) {
+ if (currentState.currentPage.ordinal + 2 > currentState.maxPage) {
navigateToCreateProfile()
} else {
reduce { copy(currentPage = OnboardingPage.entries[currentPage.ordinal + 1]) }
diff --git a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/mvi/OnboardingUiState.kt b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/mvi/OnboardingUiState.kt
index 0106d7f3..62a72e05 100644
--- a/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/mvi/OnboardingUiState.kt
+++ b/feat/onboarding/src/main/kotlin/com/team/bottles/feat/onboarding/mvi/OnboardingUiState.kt
@@ -4,7 +4,7 @@ import com.team.bottles.core.common.UiState
data class OnboardingUiState(
val currentPage: OnboardingPage = OnboardingPage.ONE,
- val maxPage: Int = OnboardingPage.entries.size + 1,
+ val maxPage: Int = OnboardingPage.entries.size,
): UiState
enum class OnboardingPage(val title: String) {
diff --git a/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/PingPongScreen.kt b/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/PingPongScreen.kt
index e8ceaf6a..132e85df 100644
--- a/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/PingPongScreen.kt
+++ b/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/PingPongScreen.kt
@@ -31,7 +31,7 @@ import com.team.bottles.core.designsystem.theme.BottlesTheme
import com.team.bottles.core.domain.bottle.model.PingPongLetter
import com.team.bottles.core.domain.bottle.model.PingPongMatchStatus
import com.team.bottles.core.domain.profile.model.UserProfile
-import com.team.bottles.core.ui.BottlesAlertDialog
+import com.team.bottles.core.ui.BottlesAlertDialogLeftDismissRightConfirm
import com.team.bottles.core.ui.model.AlertType
import com.team.bottles.feat.pingpong.components.PingPongBottomBar
import com.team.bottles.feat.pingpong.components.PingPongTopBar
@@ -75,11 +75,12 @@ internal fun PingPongScreen(
}
if (uiState.showDialog) {
- BottlesAlertDialog(
+ BottlesAlertDialogLeftDismissRightConfirm(
onClose = { onIntent(PingPongIntent.ClickCloseAlert) },
onConfirm = { onIntent(PingPongIntent.ClickConfirmAlert) },
- confirmText = AlertType.STOP_PING_PONG.confirmText,
- dismissText = AlertType.STOP_PING_PONG.dismissText,
+ onDismiss = { onIntent(PingPongIntent.ClickCloseAlert) },
+ confirmButtonText = AlertType.STOP_PING_PONG.confirmText,
+ dismissButtonText = AlertType.STOP_PING_PONG.dismissText,
title = AlertType.STOP_PING_PONG.title,
content = AlertType.STOP_PING_PONG.content
)
@@ -233,6 +234,7 @@ private fun PingPongScreenPreview() {
BottlesTheme {
PingPongScreen(
uiState = PingPongUiState(
+ showDialog = true,
currentTab = PingPongTab.INTRODUCTION,
pingPongMatchStatus = PingPongMatchStatus.NONE,
partnerProfile = UserProfile.sampleUserProfile(),
diff --git a/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/components/IntroductionContents.kt b/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/components/IntroductionContents.kt
index bbc5d750..5f241914 100644
--- a/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/components/IntroductionContents.kt
+++ b/feat/ping-pong/src/main/kotlin/com/team/bottles/feat/pingpong/components/IntroductionContents.kt
@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.team.bottles.core.designsystem.R
-import com.team.bottles.core.designsystem.components.etc.UserInfo
+import com.team.bottles.core.designsystem.components.etc.BottlesUserInfo
import com.team.bottles.core.designsystem.theme.BottlesTheme
import com.team.bottles.core.domain.profile.model.UserProfile
import com.team.bottles.core.ui.CardProfile
@@ -31,7 +31,7 @@ internal fun LazyListScope.introductionContents(
item(key = "Introduction Contents") {
when (isStoppedPingPong) {
false -> {
- UserInfo(
+ BottlesUserInfo(
imageUrl = partnerProfile.imageUrl,
userName = partnerProfile.userName,
userAge = partnerProfile.age
diff --git a/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/IntroductionScreen.kt b/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/IntroductionScreen.kt
index 4825b530..50a977ce 100644
--- a/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/IntroductionScreen.kt
+++ b/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/IntroductionScreen.kt
@@ -75,9 +75,10 @@ internal fun IntroductionScreen(
modifier = Modifier.background(color = BottlesTheme.color.background.primary),
leadingIcon = {
Icon(
- modifier = Modifier.noRippleClickable(
- onClick = { onIntent(IntroductionIntent.ClickBackButton) }
- ),
+ modifier = Modifier
+ .noRippleClickable(
+ onClick = { onIntent(IntroductionIntent.ClickBackButton) }
+ ),
painter = painterResource(id = R.drawable.ic_arrow_left_24),
contentDescription = null,
tint = BottlesTheme.color.icon.primary
diff --git a/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/component/SelecteImage.kt b/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/component/SelecteImage.kt
index 88b1705e..f7ed85d1 100644
--- a/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/component/SelecteImage.kt
+++ b/feat/profile/src/main/kotlin/com/team/bottles/feat/profile/introduction/component/SelecteImage.kt
@@ -29,6 +29,7 @@ import com.skydoves.landscapist.coil.CoilImage
import com.team.bottles.core.common.extension.toFile
import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.components.buttons.BottlesIconButton
+import com.team.bottles.core.designsystem.components.buttons.IconButtonType
import com.team.bottles.core.designsystem.theme.BottlesTheme
import com.team.bottles.feat.profile.introduction.mvi.IntroductionIntent
import kotlinx.coroutines.Dispatchers
@@ -87,6 +88,7 @@ internal fun SelectImageCard(
.offset(x = (-16).dp, y = 16.dp)
.align(Alignment.TopEnd),
icon = R.drawable.ic_close_16,
+ iconButtonType = IconButtonType.RECTANGLE,
onClick = { onIntent(IntroductionIntent.ClickDeleteButton) }
)
} else {
diff --git a/feat/report/src/main/kotlin/com/team/bottles/feat/report/ReportScreen.kt b/feat/report/src/main/kotlin/com/team/bottles/feat/report/ReportScreen.kt
index 2db5bf7d..33c05914 100644
--- a/feat/report/src/main/kotlin/com/team/bottles/feat/report/ReportScreen.kt
+++ b/feat/report/src/main/kotlin/com/team/bottles/feat/report/ReportScreen.kt
@@ -1,6 +1,5 @@
package com.team.bottles.feat.report
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
@@ -26,11 +25,12 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.components.bars.BottlesTopBar
-import com.team.bottles.core.designsystem.components.etc.UserInfo
+import com.team.bottles.core.designsystem.components.etc.BottlesUserInfo
import com.team.bottles.core.designsystem.components.textfield.BottlesLineTextFieldWithTrailingIcon
import com.team.bottles.core.designsystem.modifier.noRippleClickable
import com.team.bottles.core.designsystem.theme.BottlesTheme
-import com.team.bottles.core.ui.BottlesAlertDialog
+import com.team.bottles.core.ui.BottlesAlertDialogLeftDismissRightConfirm
+import com.team.bottles.core.ui.model.AlertType
import com.team.bottles.feat.report.components.ReportBottomBar
import com.team.bottles.feat.report.mvi.ReportIntent
import com.team.bottles.feat.report.mvi.ReportUiState
@@ -49,13 +49,14 @@ internal fun ReportScreen(
}
if (uiState.showDialog) {
- BottlesAlertDialog(
+ BottlesAlertDialogLeftDismissRightConfirm(
onClose = { onIntent(ReportIntent.ClickDialogConfirm) },
onConfirm = { onIntent(ReportIntent.ClickDialogCancel) },
- confirmText = "신고하기",
- dismissText = "계속하기",
- title = "신고하기",
- content = "접수 후 취소할 수 없으며 해당 사용자는 차단되요.\n정말 신고하시겠어요?"
+ onDismiss = { onIntent(ReportIntent.ClickDialogConfirm) },
+ confirmButtonText = AlertType.USER_REPORT.confirmText,
+ dismissButtonText = AlertType.USER_REPORT.dismissText,
+ title = AlertType.USER_REPORT.title,
+ content = AlertType.USER_REPORT.content,
)
}
@@ -105,7 +106,7 @@ internal fun ReportScreen(
Spacer(modifier = Modifier.height(height = BottlesTheme.spacing.doubleExtraLarge))
- UserInfo(
+ BottlesUserInfo(
imageUrl = uiState.userImageUrl,
userName = uiState.userName,
userAge = uiState.userAge
@@ -143,6 +144,7 @@ private fun ReportScreenPreview() {
BottlesTheme {
ReportScreen(
uiState = ReportUiState(
+ showDialog = true,
userName = "뇽뇽이",
userAge = 15
),
diff --git a/feat/sandbeach/build.gradle.kts b/feat/sandbeach/build.gradle.kts
index a21313f9..f7e81be0 100644
--- a/feat/sandbeach/build.gradle.kts
+++ b/feat/sandbeach/build.gradle.kts
@@ -8,6 +8,10 @@ plugins {
android {
namespace = "com.team.bottles.feat.sandbeach"
+
+ defaultConfig {
+ buildConfigField("Integer", "VERSION_CODE", libs.versions.versionCode.get())
+ }
}
dependencies {
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachRoute.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachRoute.kt
index fc327cd4..8a83e6f2 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachRoute.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachRoute.kt
@@ -1,7 +1,10 @@
package com.team.bottles.feat.sandbeach
import android.Manifest
+import android.content.ActivityNotFoundException
+import android.content.Intent
import android.content.pm.PackageManager
+import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -29,19 +32,26 @@ internal fun SandBeachRoute(
val context = LocalContext.current
val notificationPermissionGranted =
remember {
- ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
+ } else {
+ true
+ }
}
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
+ viewModel.confirmPermission()
Toast.makeText(context, "알림에 동의 하였습니다.", Toast.LENGTH_SHORT).show()
}
}
LaunchedEffect(notificationPermissionGranted) {
if (!notificationPermissionGranted) {
- permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ }
}
}
@@ -51,6 +61,15 @@ internal fun SandBeachRoute(
is SandBeachSideEffect.NavigateToIntroduction -> navigateToIntroduction()
is SandBeachSideEffect.NavigateToArrivedBottle -> navigateToArrivedBottles()
is SandBeachSideEffect.NavigateToBottleBox -> navigateToBottleBox()
+ is SandBeachSideEffect.NavigateToPlayStore -> {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.team.bottles&hl=ko"))
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.team.bottles&hl=ko"))
+ context.startActivity(webIntent)
+ }
+ }
}
}
}
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachScreen.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachScreen.kt
index 0669a288..4ed26861 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachScreen.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachScreen.kt
@@ -21,6 +21,7 @@ import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.components.bars.BottlesTopBar
import com.team.bottles.core.designsystem.modifier.debounceNoRippleClickable
import com.team.bottles.core.designsystem.theme.BottlesTheme
+import com.team.bottles.core.ui.BottlesAlertConfirmDialog
import com.team.bottles.feat.sandbeach.component.BottleStatusMessage
import com.team.bottles.feat.sandbeach.component.InArrivedBottle
import com.team.bottles.feat.sandbeach.component.InBottleBox
@@ -35,6 +36,16 @@ internal fun SandBeachScreen(
uiState: SandBeachUiState,
onIntent: (SandBeachIntent) -> Unit
) {
+ if (uiState.showDialog) {
+ BottlesAlertConfirmDialog(
+ onClose = { /* 닫기 없음 */ },
+ onConfirm = { onIntent(SandBeachIntent.ClickConfirmButton) },
+ confirmButtonText = "업데이트 하기",
+ title = "업데이트 안내",
+ content = "최적의 사용 환경을 위해\n최신 버전의 앱으로 업데이트 해주세요",
+ )
+ }
+
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -107,7 +118,9 @@ private fun SandBeachScreenPreview() {
contentScale = ContentScale.FillWidth
)
SandBeachScreen(
- uiState = SandBeachUiState(bottleStatus = BottleStatus.IN_ARRIVED_BOTTLE),
+ uiState = SandBeachUiState(
+ bottleStatus = BottleStatus.IN_ARRIVED_BOTTLE,
+ ),
onIntent = {}
)
}
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachViewModel.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachViewModel.kt
index fdf849b8..5235c73e 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachViewModel.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/SandBeachViewModel.kt
@@ -2,11 +2,14 @@ package com.team.bottles.feat.sandbeach
import androidx.lifecycle.SavedStateHandle
import com.team.bottles.core.common.BaseViewModel
+import com.team.bottles.core.domain.auth.usecase.GetRequiredAppVersionUseCase
import com.team.bottles.core.domain.bottle.usecase.GetBottleListUseCase
import com.team.bottles.core.domain.bottle.usecase.GetPingPongListUseCase
import com.team.bottles.core.domain.profile.model.UserProfileStatus
-import com.team.bottles.core.domain.profile.usecase.GetUserIntroductionStatusUseCase
import com.team.bottles.core.domain.profile.usecase.GetUserProfileStatusUseCase
+import com.team.bottles.core.domain.user.model.Notification
+import com.team.bottles.core.domain.user.model.NotificationType
+import com.team.bottles.core.domain.user.usecase.UpdateSettingNotificationUseCase
import com.team.bottles.feat.sandbeach.mvi.BottleStatus
import com.team.bottles.feat.sandbeach.mvi.SandBeachIntent
import com.team.bottles.feat.sandbeach.mvi.SandBeachSideEffect
@@ -19,11 +22,20 @@ class SandBeachViewModel @Inject constructor(
private val getUserProfileStatusUseCase: GetUserProfileStatusUseCase,
private val getBottleListUseCase: GetBottleListUseCase,
private val getPingPongListUseCase: GetPingPongListUseCase,
+ private val getRequiredAppVersionUseCase: GetRequiredAppVersionUseCase,
+ private val updateSettingNotificationUseCase: UpdateSettingNotificationUseCase,
savedStateHandle: SavedStateHandle
) : BaseViewModel(savedStateHandle) {
init {
setSandBeachState()
+ launch {
+ val requiredAppVersion = getRequiredAppVersionUseCase()
+
+ if (requiredAppVersion > currentState.appVersionCode) {
+ reduce { copy(showDialog = true) }
+ }
+ }
}
override fun createInitialState(savedStateHandle: SavedStateHandle): SandBeachUiState =
@@ -84,6 +96,7 @@ class SandBeachViewModel @Inject constructor(
when (intent) {
is SandBeachIntent.ClickCreateIntroductionButton -> navigateToIntroduction()
is SandBeachIntent.ClickSandBeach -> onClickSandBeach()
+ is SandBeachIntent.ClickConfirmButton -> navigateToPlayStore()
}
}
@@ -108,4 +121,21 @@ class SandBeachViewModel @Inject constructor(
postSideEffect(SandBeachSideEffect.NavigateToArrivedBottle)
}
+ private fun navigateToPlayStore() {
+ postSideEffect(SandBeachSideEffect.NavigateToPlayStore)
+ }
+
+ fun confirmPermission() {
+ launch {
+ NotificationType.entries.forEach { type ->
+ updateSettingNotificationUseCase(
+ notification = Notification(
+ notificationType = type,
+ enabled = true
+ )
+ )
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachIntent.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachIntent.kt
index bd6d64f1..a32ba0c1 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachIntent.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachIntent.kt
@@ -8,4 +8,6 @@ sealed interface SandBeachIntent : UiIntent {
data object ClickSandBeach : SandBeachIntent
+ data object ClickConfirmButton : SandBeachIntent
+
}
\ No newline at end of file
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachSideEffect.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachSideEffect.kt
index 7ab10f9b..3109ec55 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachSideEffect.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachSideEffect.kt
@@ -10,4 +10,6 @@ sealed interface SandBeachSideEffect : UiSideEffect {
data object NavigateToBottleBox : SandBeachSideEffect
+ data object NavigateToPlayStore : SandBeachSideEffect
+
}
\ No newline at end of file
diff --git a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachUiState.kt b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachUiState.kt
index 4d3c4235..ac0ad550 100644
--- a/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachUiState.kt
+++ b/feat/sandbeach/src/main/kotlin/com/team/bottles/feat/sandbeach/mvi/SandBeachUiState.kt
@@ -1,12 +1,15 @@
package com.team.bottles.feat.sandbeach.mvi
import com.team.bottles.core.common.UiState
+import com.team.bottles.feat.sandbeach.BuildConfig
data class SandBeachUiState(
+ val showDialog: Boolean = false,
val bottleStatus: BottleStatus = BottleStatus.NONE_BOTTLE,
val newBottleValue: Int = 0,
val bottleBoxValue: Int = 0,
- val afterArrivedTime: Int = 0
+ val afterArrivedTime: Int = 0,
+ val appVersionCode: Int = BuildConfig.VERSION_CODE
): UiState
enum class BottleStatus {
@@ -16,8 +19,3 @@ enum class BottleStatus {
NONE_BOTTLE,
;
}
-
-// 자기소개 받아야 하는 상태
-// 도착한보틀 O, 보틀 보관함 X - 1순위
-// 도착한보틀 x, 보틀 보관함 O - 2순위
-// 도착한보틀 x, 보틀 보관함 x - 3순위
\ No newline at end of file
diff --git a/feat/setting/.gitignore b/feat/setting/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feat/setting/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feat/setting/build.gradle.kts b/feat/setting/build.gradle.kts
new file mode 100644
index 00000000..3be08d95
--- /dev/null
+++ b/feat/setting/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("team.bottles.android.library.compose")
+ id("team.bottles.android.library")
+ id("team.bottles.android.feature")
+ id("team.bottles.android.hilt")
+ id("team.bottles.kotlin.serialization")
+}
+
+android {
+ namespace = "com.team.bottles.feat.setting"
+}
+
+dependencies {
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingRoute.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingRoute.kt
new file mode 100644
index 00000000..352affcb
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingRoute.kt
@@ -0,0 +1,32 @@
+package com.team.bottles.feat.setting.account
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.team.bottles.feat.setting.account.mvi.AccountSettingSideEffect
+import com.team.bottles.feat.setting.notification.mvi.NotificationSideEffect
+
+@Composable
+internal fun AccountSettingRoute(
+ viewModel: AccountSettingViewModel = hiltViewModel(),
+ navigateToLoginEndpoint: () -> Unit,
+ navigateToMyPage: () -> Unit,
+) {
+ val uiState by viewModel.state.collectAsStateWithLifecycle()
+
+ LaunchedEffect(Unit) {
+ viewModel.sideEffect.collect { sideEffect ->
+ when (sideEffect) {
+ is AccountSettingSideEffect.NavigateToLoginEndpoint -> navigateToLoginEndpoint()
+ is AccountSettingSideEffect.NavigateToMyPage -> navigateToMyPage()
+ }
+ }
+ }
+
+ AccountSettingScreen(
+ uiState = uiState,
+ onIntent = { intent -> viewModel.intent(intent) },
+ )
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingScreen.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingScreen.kt
new file mode 100644
index 00000000..f3e9efef
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingScreen.kt
@@ -0,0 +1,76 @@
+package com.team.bottles.feat.setting.account
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.R
+import com.team.bottles.core.designsystem.components.bars.BottlesTopBar
+import com.team.bottles.core.designsystem.modifier.noRippleClickable
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+import com.team.bottles.core.ui.BottlesAlertDialogLeftConfirmRightDismiss
+import com.team.bottles.feat.setting.account.mvi.AccountSettingIntent
+import com.team.bottles.feat.setting.account.mvi.AccountSettingUiState
+import com.team.bottles.feat.setting.components.AccountSetting
+
+@Composable
+internal fun AccountSettingScreen(
+ uiState: AccountSettingUiState,
+ onIntent: (AccountSettingIntent) -> Unit,
+) {
+ if (uiState.showDialog) {
+ BottlesAlertDialogLeftConfirmRightDismiss(
+ onClose = { onIntent(AccountSettingIntent.ClickDismissDialogButton) },
+ onDismiss = { onIntent(AccountSettingIntent.ClickDismissDialogButton) },
+ onConfirm = { onIntent(AccountSettingIntent.ClickConfirmDialogButton) },
+ confirmButtonText = uiState.dialogType.confirmText,
+ dismissButtonText = uiState.dialogType.dismissText,
+ title = uiState.dialogType.title,
+ content = uiState.dialogType.content
+ )
+ }
+
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ BottlesTopBar(
+ leadingIcon = {
+ Icon(
+ modifier = Modifier
+ .noRippleClickable(onClick = { onIntent(AccountSettingIntent.ClickBackButton) }),
+ painter = painterResource(id = R.drawable.ic_arrow_left_24),
+ contentDescription = null,
+ tint = BottlesTheme.color.icon.primary
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(height = 32.dp))
+
+ AccountSetting(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ isMatchingActive = uiState.isMatchingActive,
+ onChangeMatchingActive = { onIntent(AccountSettingIntent.ClickMatchingActiveToggleButton) },
+ onClickLogOut = { onIntent(AccountSettingIntent.ClickLogOutButton) },
+ onClickDeleteUser = { onIntent(AccountSettingIntent.ClickDeleteUserButton) },
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun AccountSettingScreenPreview() {
+ BottlesTheme {
+ AccountSettingScreen(
+ uiState = AccountSettingUiState(),
+ onIntent = {},
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingViewModel.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingViewModel.kt
new file mode 100644
index 00000000..bda960fb
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/AccountSettingViewModel.kt
@@ -0,0 +1,84 @@
+package com.team.bottles.feat.setting.account
+
+import androidx.lifecycle.SavedStateHandle
+import com.team.bottles.core.common.BaseViewModel
+import com.team.bottles.core.domain.auth.usecase.DeleteUserUseCase
+import com.team.bottles.core.domain.auth.usecase.LogOutUseCase
+import com.team.bottles.feat.setting.account.mvi.AccountSettingIntent
+import com.team.bottles.feat.setting.account.mvi.AccountSettingSideEffect
+import com.team.bottles.feat.setting.account.mvi.AccountSettingUiState
+import com.team.bottles.feat.setting.account.mvi.SettingAlertDialogType
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class AccountSettingViewModel @Inject constructor(
+ private val logOutUseCase: LogOutUseCase,
+ private val deleteUserUseCase: DeleteUserUseCase,
+ savedStateHandle: SavedStateHandle
+) : BaseViewModel(
+ savedStateHandle
+) {
+ override fun createInitialState(savedStateHandle: SavedStateHandle): AccountSettingUiState =
+ AccountSettingUiState()
+
+ override fun handleClientException(throwable: Throwable) {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun handleIntent(intent: AccountSettingIntent) {
+ when (intent) {
+ is AccountSettingIntent.ClickConfirmDialogButton -> confirm()
+ is AccountSettingIntent.ClickDismissDialogButton -> dismiss()
+ is AccountSettingIntent.ClickLogOutButton -> showLogoutDialog()
+ is AccountSettingIntent.ClickDeleteUserButton -> showDeleteUserDialog()
+ is AccountSettingIntent.ClickBackButton -> navigateToMyPage()
+ is AccountSettingIntent.ClickMatchingActiveToggleButton -> changeMatchingActive()
+ }
+ }
+
+ private fun confirm() {
+ launch {
+ when (currentState.dialogType) {
+ SettingAlertDialogType.LOG_OUT -> logOutUseCase()
+ SettingAlertDialogType.DELETE_USER -> deleteUserUseCase()
+ }
+ reduce { copy(showDialog = false) }
+ postSideEffect(AccountSettingSideEffect.NavigateToLoginEndpoint)
+ }
+ }
+
+ private fun dismiss() {
+ reduce { copy(showDialog = false) }
+ }
+
+ private fun showLogoutDialog() {
+ reduce {
+ copy(
+ showDialog = true,
+ dialogType = SettingAlertDialogType.LOG_OUT
+ )
+ }
+ }
+
+ private fun showDeleteUserDialog() {
+ reduce {
+ copy(
+ showDialog = true,
+ dialogType = SettingAlertDialogType.DELETE_USER
+ )
+ }
+ }
+
+ private fun navigateToMyPage() {
+ postSideEffect(AccountSettingSideEffect.NavigateToMyPage)
+ }
+
+ private fun changeMatchingActive() {
+ launch {
+ // TODO : 매칭 활성화 on/off 로직
+ reduce { copy(isMatchingActive = !isMatchingActive) }
+ }
+ }
+
+}
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingIntent.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingIntent.kt
new file mode 100644
index 00000000..70994806
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingIntent.kt
@@ -0,0 +1,19 @@
+package com.team.bottles.feat.setting.account.mvi
+
+import com.team.bottles.core.common.UiIntent
+
+sealed interface AccountSettingIntent : UiIntent {
+
+ data object ClickBackButton : AccountSettingIntent
+
+ data object ClickMatchingActiveToggleButton : AccountSettingIntent
+
+ data object ClickLogOutButton : AccountSettingIntent
+
+ data object ClickDeleteUserButton : AccountSettingIntent
+
+ data object ClickConfirmDialogButton : AccountSettingIntent
+
+ data object ClickDismissDialogButton : AccountSettingIntent
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingSideEffect.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingSideEffect.kt
new file mode 100644
index 00000000..36ed8182
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingSideEffect.kt
@@ -0,0 +1,11 @@
+package com.team.bottles.feat.setting.account.mvi
+
+import com.team.bottles.core.common.UiSideEffect
+
+sealed interface AccountSettingSideEffect : UiSideEffect {
+
+ data object NavigateToMyPage : AccountSettingSideEffect
+
+ data object NavigateToLoginEndpoint : AccountSettingSideEffect
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingUiState.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingUiState.kt
new file mode 100644
index 00000000..8acd3f99
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/account/mvi/AccountSettingUiState.kt
@@ -0,0 +1,31 @@
+package com.team.bottles.feat.setting.account.mvi
+
+import com.team.bottles.core.common.UiState
+
+data class AccountSettingUiState(
+ val showDialog: Boolean = false,
+ val dialogType: SettingAlertDialogType = SettingAlertDialogType.LOG_OUT,
+ val isMatchingActive: Boolean = false,
+) : UiState
+
+enum class SettingAlertDialogType(
+ val title: String,
+ val content: String,
+ val dismissText: String,
+ val confirmText: String
+) {
+ LOG_OUT(
+ title = "로그아웃",
+ content = "정말 로그아웃 하시겠어요?",
+ confirmText = "로그아웃하기",
+ dismissText = "취소하기"
+ ),
+ DELETE_USER(
+ title = "탈퇴하기",
+ content = "탈퇴 시 계정 복구가 어려워요.\n" +
+ "정말 탈퇴하시겠어요?",
+ confirmText = "탈퇴하기",
+ dismissText = "취소하기"
+ ),
+ ;
+}
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/AccountSetting.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/AccountSetting.kt
new file mode 100644
index 00000000..6b80297b
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/AccountSetting.kt
@@ -0,0 +1,57 @@
+package com.team.bottles.feat.setting.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.team.bottles.core.designsystem.components.cards.BottlesCard
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithArrow
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithToggleButton
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+internal fun AccountSetting(
+ modifier: Modifier = Modifier,
+ isMatchingActive: Boolean,
+ onChangeMatchingActive: () -> Unit,
+ onClickLogOut: () -> Unit,
+ onClickDeleteUser: () -> Unit,
+) {
+ BottlesCard(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.large
+ )
+ ) {
+// BottlesSettingItemWithToggleButton(
+// title = "매칭 활성화",
+// subTitle = "비활성화 시 다른 사람을 추천 받을 수 없고\n" +
+// "회원님도 다른 사람에게 추천되지 않아요",
+// checked = isMatchingActive,
+// onCheckedChange = onChangeMatchingActive
+// ) TODO : 매칭 활성화 API 구현시 UI 수정
+
+ BottlesSettingItemWithArrow(
+ title = "로그아웃",
+ onClickItem = onClickLogOut
+ )
+
+ BottlesSettingItemWithArrow(
+ title = "탈퇴하기",
+ onClickItem = onClickDeleteUser
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun AccountSettingPreview() {
+ BottlesTheme {
+ AccountSetting(
+ isMatchingActive = true,
+ onChangeMatchingActive = {},
+ onClickDeleteUser = {},
+ onClickLogOut = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/NotificationSetting.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/NotificationSetting.kt
new file mode 100644
index 00000000..a5a591ec
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/components/NotificationSetting.kt
@@ -0,0 +1,82 @@
+package com.team.bottles.feat.setting.components
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.components.cards.BottlesCard
+import com.team.bottles.core.designsystem.components.lists.BottlesSettingItemWithToggleButton
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+
+@Composable
+internal fun NotificationSetting(
+ modifier: Modifier = Modifier,
+ isFloatingBottle: Boolean,
+ isGoodFeelingArrived: Boolean,
+ isConversation: Boolean,
+ isMarketingResponse: Boolean,
+ onChangeFloatingBottle: () -> Unit,
+ onChangeGoodFeelingArrived: () -> Unit,
+ onChangeConversation: () -> Unit,
+ onChangeMarketingResponse: () -> Unit,
+) {
+ BottlesCard(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(
+ space = BottlesTheme.spacing.large
+ )
+ ) {
+ BottlesSettingItemWithToggleButton(
+ title = "떠다니는 보틀 알림",
+ subTitle = "매일 랜덤으로 추천되는 보틀 안내",
+ checked = isFloatingBottle,
+ onCheckedChange = onChangeFloatingBottle
+ )
+
+ BottlesSettingItemWithToggleButton(
+ title = "호감 도착 알림",
+ subTitle = "내가 받은 호감 안내",
+ checked = isGoodFeelingArrived,
+ onCheckedChange = onChangeGoodFeelingArrived
+ )
+
+ BottlesSettingItemWithToggleButton(
+ title = "대화 알림",
+ subTitle = "가치관 문답 시작 · 진행 · 중단 , 매칭 안내",
+ checked = isConversation,
+ onCheckedChange = onChangeConversation
+ )
+
+ HorizontalDivider(
+ modifier = Modifier.fillMaxWidth(),
+ color = BottlesTheme.color.border.secondary,
+ thickness = 1.dp
+ )
+
+ BottlesSettingItemWithToggleButton(
+ title = "마케팅 수신 동의",
+ checked = isMarketingResponse,
+ onCheckedChange = onChangeMarketingResponse
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun AccountSettingPreview() {
+ BottlesTheme {
+ NotificationSetting(
+ isFloatingBottle = true,
+ isGoodFeelingArrived = true,
+ isConversation = true,
+ isMarketingResponse = true,
+ onChangeFloatingBottle = {},
+ onChangeGoodFeelingArrived = {},
+ onChangeConversation = {},
+ onChangeMarketingResponse = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/navigation/SettingNavigation.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/navigation/SettingNavigation.kt
new file mode 100644
index 00000000..00b2f875
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/navigation/SettingNavigation.kt
@@ -0,0 +1,29 @@
+package com.team.bottles.feat.setting.navigation
+
+import SettingNavigator
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import com.team.bottles.feat.setting.account.AccountSettingRoute
+import com.team.bottles.feat.setting.notification.NotificationSettingRoute
+
+fun NavGraphBuilder.accountSettingScreen(
+ navigateToLoginEndpoint: () -> Unit,
+ navigateToMyPage: () -> Unit,
+) {
+ composable {
+ AccountSettingRoute(
+ navigateToLoginEndpoint = navigateToLoginEndpoint,
+ navigateToMyPage = navigateToMyPage,
+ )
+ }
+}
+
+fun NavGraphBuilder.notificationSettingScreen(
+ navigateToMyPage: () -> Unit,
+) {
+ composable {
+ NotificationSettingRoute(
+ navigateToMyPage = navigateToMyPage
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingRoute.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingRoute.kt
new file mode 100644
index 00000000..894d475c
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingRoute.kt
@@ -0,0 +1,29 @@
+package com.team.bottles.feat.setting.notification
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.team.bottles.feat.setting.notification.mvi.NotificationSideEffect
+
+@Composable
+internal fun NotificationSettingRoute(
+ viewModel: NotificationSettingViewModel = hiltViewModel(),
+ navigateToMyPage: () -> Unit
+) {
+ val uiState by viewModel.state.collectAsStateWithLifecycle()
+
+ LaunchedEffect(Unit) {
+ viewModel.sideEffect.collect { sideEffect ->
+ if (sideEffect is NotificationSideEffect.NavigateToMyPage) {
+ navigateToMyPage()
+ }
+ }
+ }
+
+ NotificationSettingScreen(
+ uiState = uiState,
+ onIntent = { intent -> viewModel.intent(intent) },
+ )
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingScreen.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingScreen.kt
new file mode 100644
index 00000000..44881695
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingScreen.kt
@@ -0,0 +1,67 @@
+package com.team.bottles.feat.setting.notification
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.team.bottles.core.designsystem.R
+import com.team.bottles.core.designsystem.components.bars.BottlesTopBar
+import com.team.bottles.core.designsystem.modifier.noRippleClickable
+import com.team.bottles.core.designsystem.theme.BottlesTheme
+import com.team.bottles.feat.setting.components.NotificationSetting
+import com.team.bottles.feat.setting.notification.mvi.NotificationIntent
+import com.team.bottles.feat.setting.notification.mvi.NotificationUiState
+
+@Composable
+internal fun NotificationSettingScreen(
+ uiState: NotificationUiState,
+ onIntent: (NotificationIntent) -> Unit,
+) {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ BottlesTopBar(
+ leadingIcon = {
+ Icon(
+ modifier = Modifier
+ .noRippleClickable(onClick = { onIntent(NotificationIntent.ClickBackButton) }),
+ painter = painterResource(id = R.drawable.ic_arrow_left_24),
+ contentDescription = null,
+ tint = BottlesTheme.color.icon.primary
+ )
+ },
+ )
+
+ Spacer(modifier = Modifier.height(height = 32.dp))
+
+ NotificationSetting(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ isFloatingBottle = uiState.isFloatingBottle,
+ isGoodFeelingArrived = uiState.isGoodFeelingArrived,
+ isConversation = uiState.isConversation,
+ isMarketingResponse = uiState.isMarketingResponse,
+ onChangeFloatingBottle = { onIntent(NotificationIntent.ClickFloatingBottleToggleButton) },
+ onChangeGoodFeelingArrived = { onIntent(NotificationIntent.ClickGoodFeelingArrivedToggleButton) },
+ onChangeConversation = { onIntent(NotificationIntent.ClickConversationToggleButton) },
+ onChangeMarketingResponse = { onIntent(NotificationIntent.ClickMarketingResponseToggleButton) },
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun NotificationSettingScreenPreview() {
+ BottlesTheme {
+ NotificationSettingScreen(
+ uiState = NotificationUiState(),
+ onIntent = {},
+ )
+ }
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingViewModel.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingViewModel.kt
new file mode 100644
index 00000000..efe788cb
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/NotificationSettingViewModel.kt
@@ -0,0 +1,111 @@
+package com.team.bottles.feat.setting.notification
+
+import androidx.lifecycle.SavedStateHandle
+import com.team.bottles.core.common.BaseViewModel
+import com.team.bottles.core.domain.user.model.Notification
+import com.team.bottles.core.domain.user.model.NotificationType
+import com.team.bottles.core.domain.user.usecase.GetSettingNotificationsUseCase
+import com.team.bottles.core.domain.user.usecase.UpdateSettingNotificationUseCase
+import com.team.bottles.feat.setting.notification.mvi.NotificationIntent
+import com.team.bottles.feat.setting.notification.mvi.NotificationSideEffect
+import com.team.bottles.feat.setting.notification.mvi.NotificationUiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class NotificationSettingViewModel @Inject constructor(
+ private val getSettingNotificationsUseCase: GetSettingNotificationsUseCase,
+ private val updateSettingNotificationUseCase: UpdateSettingNotificationUseCase,
+ savedStateHandle: SavedStateHandle
+) : BaseViewModel(savedStateHandle) {
+
+ init {
+ launch {
+ val notifications = getSettingNotificationsUseCase()
+
+ val isConversation = notifications.find { it.notificationType == NotificationType.PING_PONG }?.enabled?: false
+ val isMarketingResponse = notifications.find { it.notificationType == NotificationType.MARKETING }?.enabled?: false
+ val isFloatingBottle = notifications.find { it.notificationType == NotificationType.DAILY_RANDOM }?.enabled?: false
+ val isGoodFeelingArrived = notifications.find { it.notificationType == NotificationType.RECEIVE_LIKE }?.enabled?: false
+
+ reduce {
+ copy(
+ isConversation = isConversation,
+ isMarketingResponse = isMarketingResponse,
+ isFloatingBottle = isFloatingBottle,
+ isGoodFeelingArrived = isGoodFeelingArrived
+ )
+ }
+ }
+ }
+
+ override fun createInitialState(savedStateHandle: SavedStateHandle): NotificationUiState =
+ NotificationUiState()
+
+ override suspend fun handleIntent(intent: NotificationIntent) {
+ when (intent) {
+ is NotificationIntent.ClickBackButton -> navigateToMyPage()
+ is NotificationIntent.ClickConversationToggleButton -> changeConversationNotification()
+ is NotificationIntent.ClickFloatingBottleToggleButton -> changeFloatingBottleNotification()
+ is NotificationIntent.ClickMarketingResponseToggleButton -> changeMarketingResponseNotification()
+ is NotificationIntent.ClickGoodFeelingArrivedToggleButton -> changeGoodFeelingArrivedNotification()
+ }
+ }
+
+ override fun handleClientException(throwable: Throwable) {
+ TODO("Not yet implemented")
+ }
+
+ private fun navigateToMyPage() {
+ postSideEffect(NotificationSideEffect.NavigateToMyPage)
+ }
+
+ private fun changeFloatingBottleNotification() {
+ launch {
+ updateSettingNotificationUseCase(
+ notification = Notification(
+ notificationType = NotificationType.DAILY_RANDOM,
+ enabled = !currentState.isFloatingBottle
+ )
+ )
+ reduce { copy(isFloatingBottle = !isFloatingBottle) }
+ }
+ }
+
+ private fun changeGoodFeelingArrivedNotification() {
+ launch {
+ updateSettingNotificationUseCase(
+ notification = Notification(
+ notificationType = NotificationType.RECEIVE_LIKE,
+ enabled = !currentState.isGoodFeelingArrived
+ )
+ )
+ reduce { copy(isGoodFeelingArrived = !isGoodFeelingArrived) }
+ }
+ }
+
+ private fun changeConversationNotification() {
+ launch {
+ updateSettingNotificationUseCase(
+ notification = Notification(
+ notificationType = NotificationType.PING_PONG,
+ enabled = !currentState.isConversation
+ )
+ )
+ reduce { copy(isConversation = !isConversation) }
+ }
+ }
+
+ private fun changeMarketingResponseNotification() {
+ launch {
+ updateSettingNotificationUseCase(
+ notification = Notification(
+ notificationType = NotificationType.MARKETING,
+ enabled = !currentState.isMarketingResponse
+ )
+ )
+ reduce { copy(isMarketingResponse = !isMarketingResponse) }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationIntent.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationIntent.kt
new file mode 100644
index 00000000..4a265249
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationIntent.kt
@@ -0,0 +1,17 @@
+package com.team.bottles.feat.setting.notification.mvi
+
+import com.team.bottles.core.common.UiIntent
+
+sealed interface NotificationIntent : UiIntent {
+
+ data object ClickBackButton : NotificationIntent
+
+ data object ClickFloatingBottleToggleButton : NotificationIntent
+
+ data object ClickGoodFeelingArrivedToggleButton : NotificationIntent
+
+ data object ClickConversationToggleButton : NotificationIntent
+
+ data object ClickMarketingResponseToggleButton : NotificationIntent
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationSideEffect.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationSideEffect.kt
new file mode 100644
index 00000000..fa30bddf
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationSideEffect.kt
@@ -0,0 +1,9 @@
+package com.team.bottles.feat.setting.notification.mvi
+
+import com.team.bottles.core.common.UiSideEffect
+
+sealed interface NotificationSideEffect : UiSideEffect {
+
+ data object NavigateToMyPage : NotificationSideEffect
+
+}
\ No newline at end of file
diff --git a/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationUiState.kt b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationUiState.kt
new file mode 100644
index 00000000..b6b4d47e
--- /dev/null
+++ b/feat/setting/src/main/kotlin/com/team/bottles/feat/setting/notification/mvi/NotificationUiState.kt
@@ -0,0 +1,10 @@
+package com.team.bottles.feat.setting.notification.mvi
+
+import com.team.bottles.core.common.UiState
+
+data class NotificationUiState(
+ val isFloatingBottle: Boolean = false,
+ val isGoodFeelingArrived: Boolean = false,
+ val isConversation: Boolean = false,
+ val isMarketingResponse: Boolean = false,
+) : UiState
diff --git a/feat/splash/build.gradle.kts b/feat/splash/build.gradle.kts
index f34690b0..e6b413a3 100644
--- a/feat/splash/build.gradle.kts
+++ b/feat/splash/build.gradle.kts
@@ -8,6 +8,10 @@ plugins {
android {
namespace = "com.team.bottles.feat.splash"
+
+ defaultConfig {
+ buildConfigField("Integer", "VERSION_CODE", libs.versions.versionCode.get())
+ }
}
dependencies {
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashRoute.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashRoute.kt
index b17e6ef5..9e808326 100644
--- a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashRoute.kt
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashRoute.kt
@@ -1,8 +1,15 @@
package com.team.bottles.feat.splash
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
+import com.team.bottles.feat.splash.mvi.SplashSideEffect
@Composable
fun SplashRoute(
@@ -11,15 +18,30 @@ fun SplashRoute(
navigateToSandBeach: () -> Unit,
navigateToOnboarding: () -> Unit
) {
+ val uiState by viewModel.state.collectAsState()
+ val context = LocalContext.current
+
LaunchedEffect(Unit) {
viewModel.sideEffect.collect { sideEffect ->
when (sideEffect) {
is SplashSideEffect.NavigateToSandBeach -> navigateToSandBeach()
is SplashSideEffect.NavigateToLoginEndpoint -> navigateToLoginEndpoint()
is SplashSideEffect.NavigateToOnboarding -> navigateToOnboarding()
+ is SplashSideEffect.NavigateToPlayStore -> {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.team.bottles&hl=ko"))
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.team.bottles&hl=ko"))
+ context.startActivity(webIntent)
+ }
+ }
}
}
}
- SplashScreen()
+ SplashScreen(
+ uiState = uiState,
+ onIntent = { intent -> viewModel.intent(intent = intent) }
+ )
}
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashScreen.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashScreen.kt
index 98c1a597..71d200ae 100644
--- a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashScreen.kt
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashScreen.kt
@@ -15,9 +15,25 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.team.bottles.core.designsystem.R
import com.team.bottles.core.designsystem.theme.BottlesTheme
+import com.team.bottles.core.ui.BottlesAlertConfirmDialog
+import com.team.bottles.feat.splash.mvi.SplashIntent
+import com.team.bottles.feat.splash.mvi.SplashUiState
@Composable
-internal fun SplashScreen() {
+internal fun SplashScreen(
+ uiState: SplashUiState,
+ onIntent: (SplashIntent) -> Unit
+) {
+ if (uiState.showDialog) {
+ BottlesAlertConfirmDialog(
+ onClose = { /* 닫기 없음 */ },
+ onConfirm = { onIntent(SplashIntent.ClickConfirmButton) },
+ confirmButtonText = "업데이트 하기",
+ title = "업데이트 안내",
+ content = "최적의 사용 환경을 위해\n최신 버전의 앱으로 업데이트 해주세요",
+ )
+ }
+
Column(
modifier = Modifier
.fillMaxSize()
@@ -45,6 +61,11 @@ internal fun SplashScreen() {
@Composable
private fun SplashScreenPreview() {
BottlesTheme {
- SplashScreen()
+ SplashScreen(
+ uiState = SplashUiState(
+ showDialog = true
+ ),
+ onIntent = {}
+ )
}
}
\ No newline at end of file
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashSideEffect.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashSideEffect.kt
deleted file mode 100644
index fc0ab698..00000000
--- a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashSideEffect.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.team.bottles.feat.splash
-
-sealed interface SplashSideEffect {
-
- data object NavigateToLoginEndpoint : SplashSideEffect
-
- data object NavigateToSandBeach : SplashSideEffect
-
- data object NavigateToOnboarding : SplashSideEffect
-
-}
\ No newline at end of file
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashViewModel.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashViewModel.kt
index 8de27eef..c04507b1 100644
--- a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashViewModel.kt
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/SplashViewModel.kt
@@ -1,43 +1,62 @@
package com.team.bottles.feat.splash
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.SavedStateHandle
+import com.team.bottles.core.common.BaseViewModel
+import com.team.bottles.core.domain.auth.usecase.GetRequiredAppVersionUseCase
import com.team.bottles.core.domain.auth.usecase.WebViewConnectUseCase
import com.team.bottles.core.domain.profile.model.UserProfileStatus
import com.team.bottles.core.domain.profile.usecase.GetUserProfileStatusUseCase
+import com.team.bottles.feat.splash.mvi.SplashIntent
+import com.team.bottles.feat.splash.mvi.SplashSideEffect
+import com.team.bottles.feat.splash.mvi.SplashUiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SplashViewModel @Inject constructor(
private val getTokenStatus: WebViewConnectUseCase,
- private val getUserProfileStatusUseCase: GetUserProfileStatusUseCase
-): ViewModel() {
-
- private val _sideEffect: MutableSharedFlow = MutableSharedFlow()
- val sideEffect = _sideEffect.asSharedFlow()
+ private val getUserProfileStatusUseCase: GetUserProfileStatusUseCase,
+ private val getRequiredAppVersionUseCase: GetRequiredAppVersionUseCase,
+ savedStateHandle: SavedStateHandle,
+): BaseViewModel(savedStateHandle) {
init {
- viewModelScope.launch {
- val tokens = getTokenStatus.getLocalToken()
-
- delay(1000L)
+ launch {
+ val requiredAppVersion = getRequiredAppVersionUseCase()
- if (tokens.accessToken.isEmpty()) {
- _sideEffect.emit(SplashSideEffect.NavigateToLoginEndpoint)
+ if (requiredAppVersion > currentState.appVersionCode) {
+ reduce { copy(showDialog = true) }
} else {
- val profileStatus = getUserProfileStatusUseCase()
+ val tokens = getTokenStatus.getLocalToken()
+
+ delay(1000L)
- when (profileStatus) {
- UserProfileStatus.EMPTY -> _sideEffect.emit(SplashSideEffect.NavigateToOnboarding)
- else -> _sideEffect.emit(SplashSideEffect.NavigateToSandBeach)
+ if (tokens.accessToken.isEmpty()) {
+ postSideEffect(SplashSideEffect.NavigateToLoginEndpoint)
+ } else {
+ val profileStatus = getUserProfileStatusUseCase()
+
+ when (profileStatus) {
+ UserProfileStatus.EMPTY -> postSideEffect(SplashSideEffect.NavigateToOnboarding)
+ else -> postSideEffect(SplashSideEffect.NavigateToSandBeach)
+ }
}
}
}
}
+ override fun createInitialState(savedStateHandle: SavedStateHandle): SplashUiState =
+ SplashUiState()
+
+ override suspend fun handleIntent(intent: SplashIntent) {
+ when (intent) {
+ is SplashIntent.ClickConfirmButton -> postSideEffect(SplashSideEffect.NavigateToPlayStore)
+ }
+ }
+
+ override fun handleClientException(throwable: Throwable) {
+ TODO("Not yet implemented")
+ }
+
}
\ No newline at end of file
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashIntent.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashIntent.kt
new file mode 100644
index 00000000..718ad8a1
--- /dev/null
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashIntent.kt
@@ -0,0 +1,9 @@
+package com.team.bottles.feat.splash.mvi
+
+import com.team.bottles.core.common.UiIntent
+
+sealed interface SplashIntent : UiIntent {
+
+ data object ClickConfirmButton : SplashIntent
+
+}
\ No newline at end of file
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashSideEffect.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashSideEffect.kt
new file mode 100644
index 00000000..a7e8899f
--- /dev/null
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashSideEffect.kt
@@ -0,0 +1,15 @@
+package com.team.bottles.feat.splash.mvi
+
+import com.team.bottles.core.common.UiSideEffect
+
+sealed interface SplashSideEffect : UiSideEffect {
+
+ data object NavigateToLoginEndpoint : SplashSideEffect
+
+ data object NavigateToSandBeach : SplashSideEffect
+
+ data object NavigateToOnboarding : SplashSideEffect
+
+ data object NavigateToPlayStore : SplashSideEffect
+
+}
\ No newline at end of file
diff --git a/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashUiState.kt b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashUiState.kt
new file mode 100644
index 00000000..d4efe7b9
--- /dev/null
+++ b/feat/splash/src/main/kotlin/com/team/bottles/feat/splash/mvi/SplashUiState.kt
@@ -0,0 +1,9 @@
+package com.team.bottles.feat.splash.mvi
+
+import com.team.bottles.core.common.UiState
+import com.team.bottles.feat.splash.BuildConfig
+
+data class SplashUiState(
+ val appVersionCode: Int = BuildConfig.VERSION_CODE,
+ val showDialog: Boolean = false,
+) : UiState
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9208720b..d6511f3f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,9 +3,9 @@
compileSdk = "34"
minSdk = "28"
targetSdk = "34"
-appVersion = "0.9.5-beta"
-versionCode = "10007"
-versionName = "0.9.5"
+appVersion = "1.0.0"
+versionCode = "10008"
+versionName = "1.0.0"
# kotlin
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2f093c55..daa59b0f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -34,6 +34,7 @@ include(":core:datastore")
include(":core:ui")
include(":core:navigator")
include(":core:common")
+include(":core:local")
include(":feat:bottle")
include(":feat:sandbeach")
@@ -43,4 +44,5 @@ include(":feat:onboarding")
include(":feat:profile")
include(":feat:ping-pong")
include(":feat:splash")
-include(":feat:report")
\ No newline at end of file
+include(":feat:report")
+include(":feat:setting")
\ No newline at end of file