diff --git a/OnDotAlarmKitBridge/Sources/OnDotAlarmKitBridge/OnDotAlarmKitBridge.swift b/OnDotAlarmKitBridge/Sources/OnDotAlarmKitBridge/OnDotAlarmKitBridge.swift index 3d230cf1..dc87a6b2 100644 --- a/OnDotAlarmKitBridge/Sources/OnDotAlarmKitBridge/OnDotAlarmKitBridge.swift +++ b/OnDotAlarmKitBridge/Sources/OnDotAlarmKitBridge/OnDotAlarmKitBridge.swift @@ -163,6 +163,7 @@ public final class AlarmKitBridge: NSObject { startLat: NSNumber?, startLng: NSNumber?, endLat: NSNumber?, endLng: NSNumber?, mapProvider: String, + transportType: String, _ completion: @escaping (String?, String?) -> Void ) { Task { @@ -184,7 +185,8 @@ public final class AlarmKitBridge: NSObject { endLat: endLat?.doubleValue, endLng: endLng?.doubleValue, name: title, - mapProvider: mapProvider + mapProvider: mapProvider, + transportType: transportType ) } @@ -247,7 +249,8 @@ public final class AlarmKitBridge: NSObject { endLat: endLat?.doubleValue, endLng: endLng?.doubleValue, name: title, - mapProvider: mapProvider + mapProvider: mapProvider, + transportType: transportType ) : nil, secondaryIntent: secondaryIntent, sound: .default @@ -353,6 +356,7 @@ struct OpenMapsIntent: LiveActivityIntent { @Parameter(title: "End Longitude") var endLng: Double? @Parameter(title: "Name") var name: String? @Parameter(title: "Map Provider") var mapProvider: String? + @Parameter(title: "Transport Type") var transportType: String? init() {} @@ -364,7 +368,8 @@ struct OpenMapsIntent: LiveActivityIntent { endLat: Double?, endLng: Double?, name: String?, - mapProvider: String? + mapProvider: String?, + transportType: String? ) { self.scheduleId = scheduleId self.alarmId = alarmId @@ -374,14 +379,17 @@ struct OpenMapsIntent: LiveActivityIntent { self.endLng = endLng self.name = name self.mapProvider = mapProvider + self.transportType = transportType } @MainActor func perform() async throws -> some IntentResult { guard let sLat = startLat, let sLng = startLng, - let eLat = endLat, let eLng = endLng, let mapProvider = mapProvider else { + let eLat = endLat, let eLng = endLng else { return .result() } + let mapProvider = mapProvider ?? "kakao" + let transportType = transportType ?? "public_transport" if let ud = UserDefaults(suiteName: "group.com.dh.ondot.shared") { ud.set( @@ -394,7 +402,8 @@ struct OpenMapsIntent: LiveActivityIntent { "elng": eLng, "sname": "출발지", "ename": name ?? "도착지", - "provider": mapProvider + "provider": mapProvider, + "transportType": transportType ], forKey: "pendingOpenDirections" ) diff --git a/composeApp/src/commonMain/kotlin/com/dh/ondot/core/DirectionsFacade.kt b/composeApp/src/commonMain/kotlin/com/dh/ondot/core/DirectionsFacade.kt index a82f1a42..dc17e4d5 100644 --- a/composeApp/src/commonMain/kotlin/com/dh/ondot/core/DirectionsFacade.kt +++ b/composeApp/src/commonMain/kotlin/com/dh/ondot/core/DirectionsFacade.kt @@ -1,6 +1,7 @@ package com.dh.ondot.core import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.service.DirectionsOpener import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -17,6 +18,28 @@ object DirectionsFacade : KoinComponent { startName: String, endName: String, ) { - opener.openDirections(startLat, startLng, endLat, endLng, provider, startName, endName) + openDirections( + startLat = startLat, + startLng = startLng, + endLat = endLat, + endLng = endLng, + provider = provider, + startName = startName, + endName = endName, + transportType = TransportType.PUBLIC_TRANSPORT, + ) + } + + fun openDirections( + startLat: Double, + startLng: Double, + endLat: Double, + endLng: Double, + provider: MapProvider, + startName: String, + endName: String, + transportType: TransportType, + ) { + opener.openDirections(startLat, startLng, endLat, endLng, provider, startName, endName, transportType) } } diff --git a/core/bridge/src/commonMain/kotlin/com/ondot/bridge/DirectionsFacade.kt b/core/bridge/src/commonMain/kotlin/com/ondot/bridge/DirectionsFacade.kt index ec763623..86c6928f 100644 --- a/core/bridge/src/commonMain/kotlin/com/ondot/bridge/DirectionsFacade.kt +++ b/core/bridge/src/commonMain/kotlin/com/ondot/bridge/DirectionsFacade.kt @@ -1,6 +1,7 @@ package com.ondot.bridge import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.service.DirectionsOpener import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -17,6 +18,28 @@ object DirectionsFacade : KoinComponent { startName: String, endName: String, ) { - opener.openDirections(startLat, startLng, endLat, endLng, provider, startName, endName) + openDirections( + startLat = startLat, + startLng = startLng, + endLat = endLat, + endLng = endLng, + provider = provider, + startName = startName, + endName = endName, + transportType = TransportType.PUBLIC_TRANSPORT, + ) + } + + fun openDirections( + startLat: Double, + startLng: Double, + endLat: Double, + endLng: Double, + provider: MapProvider, + startName: String, + endName: String, + transportType: TransportType, + ) { + opener.openDirections(startLat, startLng, endLat, endLng, provider, startName, endName, transportType) } } diff --git a/core/design-system/src/commonMain/composeResources/drawable/ic_car.png b/core/design-system/src/commonMain/composeResources/drawable/ic_car.png new file mode 100644 index 00000000..14420b65 Binary files /dev/null and b/core/design-system/src/commonMain/composeResources/drawable/ic_car.png differ diff --git a/core/design-system/src/commonMain/composeResources/drawable/ic_public_transport.png b/core/design-system/src/commonMain/composeResources/drawable/ic_public_transport.png new file mode 100644 index 00000000..e75f92a5 Binary files /dev/null and b/core/design-system/src/commonMain/composeResources/drawable/ic_public_transport.png differ diff --git a/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/AlarmInfoItem.kt b/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/AlarmInfoItem.kt index eea24aad..93392b0c 100644 --- a/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/AlarmInfoItem.kt +++ b/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/AlarmInfoItem.kt @@ -34,12 +34,12 @@ fun AlarmInfoItem( info: Alarm, type: AlarmType, scheduleDate: String, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, onClick: () -> Unit = {}, onToggleSwitch: () -> Unit = {}, ) { val (period, time) = DateTimeFormatter.formatAmPmTimePair(info.triggeredAt) val isYesterday = DateTimeFormatter.isYesterday(scheduleDate, info.triggeredAt) + val interactionSource = remember { MutableInteractionSource() } Box( modifier = diff --git a/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/DateTimeInfoBar.kt b/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/DateTimeInfoBar.kt index b3ffed4a..4479925e 100644 --- a/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/DateTimeInfoBar.kt +++ b/core/design-system/src/commonMain/kotlin/com/ondot/designsystem/components/DateTimeInfoBar.kt @@ -30,11 +30,12 @@ fun DateTimeInfoBar( repeatDays: List = emptyList(), date: LocalDate?, time: LocalTime, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, backgroundColor: Color = Green900, onClickDate: () -> Unit = {}, onClickTime: () -> Unit = {}, ) { + val interactionSource = remember { MutableInteractionSource() } + Row( modifier = Modifier diff --git a/core/platform/src/androidMain/kotlin/com/ondot/platform/util/AndroidDirectionsOpener.kt b/core/platform/src/androidMain/kotlin/com/ondot/platform/util/AndroidDirectionsOpener.kt index a18105d6..3c95ac00 100644 --- a/core/platform/src/androidMain/kotlin/com/ondot/platform/util/AndroidDirectionsOpener.kt +++ b/core/platform/src/androidMain/kotlin/com/ondot/platform/util/AndroidDirectionsOpener.kt @@ -7,6 +7,7 @@ import android.net.Uri import androidx.core.net.toUri import co.touchlab.kermit.Logger import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.service.DirectionsOpener class AndroidDirectionsOpener( @@ -20,11 +21,12 @@ class AndroidDirectionsOpener( provider: MapProvider, startName: String, endName: String, + transportType: TransportType, ) { val mode = when (provider) { - MapProvider.KAKAO -> "PUBLICTRANSIT" - MapProvider.NAVER -> "public" + MapProvider.KAKAO -> transportType.toKakaoRouteMode() + MapProvider.NAVER -> transportType.toNaverRouteMode() MapProvider.APPLE -> error("unreachable") } val intent = @@ -85,4 +87,16 @@ class AndroidDirectionsOpener( } } } + + private fun TransportType.toKakaoRouteMode(): String = + when (this) { + TransportType.PUBLIC_TRANSPORT -> "PUBLICTRANSIT" + TransportType.CAR -> "CAR" + } + + private fun TransportType.toNaverRouteMode(): String = + when (this) { + TransportType.PUBLIC_TRANSPORT -> "public" + TransportType.CAR -> "car" + } } diff --git a/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosAlarmScheduler.kt b/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosAlarmScheduler.kt index 917bb4f2..b94622b9 100644 --- a/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosAlarmScheduler.kt +++ b/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosAlarmScheduler.kt @@ -105,6 +105,7 @@ class IosAlarmScheduler : AlarmScheduler { endLat = info.endLat.toNSNumber(), endLng = info.endLng.toNSNumber(), mapProvider = mapProvider.name.lowercase(), + transportType = info.transportType.name.lowercase(), ) { uuid, err -> if (err != null) { logger.e { "AlarmKit schedule FAIL: alarmId=${alarm.alarmId}, err=$err" } diff --git a/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosDirectionsOpener.kt b/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosDirectionsOpener.kt index c0c71559..8e878989 100644 --- a/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosDirectionsOpener.kt +++ b/core/platform/src/iosMain/kotlin/com/ondot/platform/util/IosDirectionsOpener.kt @@ -1,6 +1,7 @@ package com.ondot.platform.util import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.service.DirectionsOpener import com.ondot.util.toUtf8 import platform.Foundation.NSBundle @@ -19,6 +20,7 @@ class IosDirectionsOpener : DirectionsOpener { provider: MapProvider, startName: String, endName: String, + transportType: TransportType, ) { val app = UIApplication.sharedApplication @@ -41,13 +43,13 @@ class IosDirectionsOpener : DirectionsOpener { when (provider) { MapProvider.KAKAO -> { - val mode = "publictransit" + val mode = transportType.toKakaoRouteMode() val url = "kakaomap://route?sp=$startLat,$startLng&ep=$endLat,$endLng&by=$mode" val web = "https://m.map.kakao.com/scheme/route?sp=$startLat,$startLng&ep=$endLat,$endLng&by=$mode" open(url, web) } MapProvider.NAVER -> { - val mode = "public" + val mode = transportType.toNaverRouteMode() val bundleId = NSBundle.mainBundle.bundleIdentifier ?: "com.dh.ondot.iosApp" val url = "nmap://route/$mode" + @@ -58,7 +60,7 @@ class IosDirectionsOpener : DirectionsOpener { open(url, "http://itunes.apple.com/app/id311867728?mt=8") } MapProvider.APPLE -> { - val dir = "r" + val dir = transportType.toAppleDirectionFlag() // Apple 공식 Map Links val url = "http://maps.apple.com/?saddr=$startLat,$startLng&daddr=$endLat,$endLng&dirflg=$dir" open(url) @@ -69,4 +71,22 @@ class IosDirectionsOpener : DirectionsOpener { private fun ensureMain(block: () -> Unit) { if (NSThread.isMainThread) block() else dispatch_async(dispatch_get_main_queue()) { block() } } + + private fun TransportType.toKakaoRouteMode(): String = + when (this) { + TransportType.PUBLIC_TRANSPORT -> "publictransit" + TransportType.CAR -> "car" + } + + private fun TransportType.toNaverRouteMode(): String = + when (this) { + TransportType.PUBLIC_TRANSPORT -> "public" + TransportType.CAR -> "car" + } + + private fun TransportType.toAppleDirectionFlag(): String = + when (this) { + TransportType.PUBLIC_TRANSPORT -> "r" + TransportType.CAR -> "d" + } } diff --git a/core/ui/src/commonMain/kotlin/com/ondot/ui/model/TransportInfo.kt b/core/ui/src/commonMain/kotlin/com/ondot/ui/model/TransportInfo.kt new file mode 100644 index 00000000..969e2128 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/ondot/ui/model/TransportInfo.kt @@ -0,0 +1,23 @@ +package com.ondot.ui.model + +import com.ondot.domain.model.enums.TransportType +import ondot.core.design_system.generated.resources.Res +import ondot.core.design_system.generated.resources.ic_car +import ondot.core.design_system.generated.resources.ic_public_transport +import org.jetbrains.compose.resources.DrawableResource + +data class TransportInfo( + val title: String, + val icon: DrawableResource, +) + +fun TransportType.toTransportInfo(): TransportInfo = + TransportInfo( + title = if (this == TransportType.PUBLIC_TRANSPORT) "대중교통 이용" else "자가용 이용", + icon = + if (this == TransportType.PUBLIC_TRANSPORT) { + Res.drawable.ic_public_transport + } else { + Res.drawable.ic_car + }, + ) diff --git a/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/PlacePicker.kt b/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/PlacePicker.kt index d817e4c3..09c0ef05 100644 --- a/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/PlacePicker.kt +++ b/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/PlacePicker.kt @@ -1,8 +1,10 @@ package com.ondot.ui.screen.placepicker +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -11,15 +13,18 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import com.dh.ondot.presentation.ui.theme.ANDROID import com.dh.ondot.presentation.ui.theme.CREATE_SCHEDULE @@ -38,15 +43,22 @@ import com.ondot.designsystem.components.TopBar import com.ondot.designsystem.getPlatform import com.ondot.designsystem.theme.OnDotColor.Gray0 import com.ondot.designsystem.theme.OnDotColor.Gray200 +import com.ondot.designsystem.theme.OnDotColor.Gray400 +import com.ondot.designsystem.theme.OnDotColor.Gray600 import com.ondot.designsystem.theme.OnDotColor.Gray800 import com.ondot.designsystem.theme.OnDotColor.Gray900 +import com.ondot.designsystem.theme.OnDotColor.Green500 import com.ondot.domain.model.enums.ButtonType import com.ondot.domain.model.enums.OnDotTextStyle import com.ondot.domain.model.enums.RouterType import com.ondot.domain.model.enums.TopBarType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.PlaceHistory +import com.ondot.ui.model.toTransportInfo import com.ondot.ui.screen.placepicker.model.PlacePickerUiModel +import com.ondot.ui.util.noRippleClickable +import org.jetbrains.compose.resources.painterResource @Composable fun PlacePickerScreen( @@ -61,6 +73,7 @@ fun PlacePickerScreen( onHistorySelected: (PlaceHistory) -> Unit, onDeleteHistory: (PlaceHistory) -> Unit, onToggleCheckBox: () -> Unit, + onTransportTypeChanged: (TransportType) -> Unit, onNext: () -> Unit, popScreen: () -> Unit, ) { @@ -102,14 +115,21 @@ fun PlacePickerScreen( color = Gray0, ) - Spacer(modifier = Modifier.height(48.dp)) + Spacer(modifier = Modifier.height(24.dp)) + + TransportTypeSection( + currentType = state.selectedTransportType, + onClick = onTransportTypeChanged, + ) + + Spacer(modifier = Modifier.height(24.dp)) HomeDepartureOption( isChecked = state.isChecked, onClick = onToggleCheckBox, ) - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(16.dp)) RouteInputSection( departurePlaceInput = state.departurePlaceInput, @@ -179,6 +199,74 @@ fun HomeDepartureOption( } } +@Composable +private fun TransportTypeSection( + currentType: TransportType = TransportType.PUBLIC_TRANSPORT, + onClick: (TransportType) -> Unit = {}, +) { + Row( + modifier = + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + TransportItem( + modifier = Modifier.weight(1f), + type = TransportType.PUBLIC_TRANSPORT, + isSelected = currentType == TransportType.PUBLIC_TRANSPORT, + onClick = onClick, + ) + + Spacer(modifier = Modifier.width(10.dp)) + + TransportItem( + modifier = Modifier.weight(1f), + type = TransportType.CAR, + isSelected = currentType == TransportType.CAR, + onClick = onClick, + ) + } +} + +@Composable +private fun TransportItem( + modifier: Modifier = Modifier, + type: TransportType, + isSelected: Boolean, + onClick: (TransportType) -> Unit, +) { + val color = if (isSelected) Green500 else Gray400 + val backgroundColor = if (isSelected) Gray600 else Gray900 + val info = type.toTransportInfo() + + Row( + modifier = + modifier + .height(38.dp) + .background(backgroundColor, RoundedCornerShape(99.dp)) + .noRippleClickable(onClick = { onClick(type) }), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Image( + painter = painterResource(info.icon), + contentDescription = null, + colorFilter = ColorFilter.tint(color), + modifier = + Modifier + .size(24.dp), + ) + + Spacer(Modifier.width(10.dp)) + + OnDotText( + text = info.title, + style = OnDotTextStyle.BodyLargeR1, + color = color, + ) + } +} + @Composable fun PlaceList( list: List, diff --git a/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/model/PlacePickerUiModel.kt b/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/model/PlacePickerUiModel.kt index b7a6bef0..9590b583 100644 --- a/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/model/PlacePickerUiModel.kt +++ b/core/ui/src/commonMain/kotlin/com/ondot/ui/screen/placepicker/model/PlacePickerUiModel.kt @@ -2,6 +2,7 @@ package com.ondot.ui.screen.placepicker.model import androidx.compose.runtime.Immutable import com.ondot.domain.model.enums.RouterType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.PlaceHistory import com.ondot.util.DateTimeFormatter @@ -18,6 +19,7 @@ data class PlacePickerUiModel( val placeHistory: List = emptyList(), // 검색 기록 리스트, val lastFocusedTextField: RouterType = RouterType.Departure, // 두개의 텍스트 필드 중 가장 마지막으로 포커스된 텍스트 필드 val homeAddress: AddressInfo = AddressInfo(), + val selectedTransportType: TransportType = TransportType.PUBLIC_TRANSPORT, ) { companion object { fun formattedDate(date: String) = DateTimeFormatter.formatKoreanDateMonthDay(date) diff --git a/core/util/src/commonMain/kotlin/com/ondot/util/DefaultScheduleAlarmManager.kt b/core/util/src/commonMain/kotlin/com/ondot/util/DefaultScheduleAlarmManager.kt index 1fcd851b..39d29dc6 100644 --- a/core/util/src/commonMain/kotlin/com/ondot/util/DefaultScheduleAlarmManager.kt +++ b/core/util/src/commonMain/kotlin/com/ondot/util/DefaultScheduleAlarmManager.kt @@ -214,6 +214,7 @@ private fun Schedule.toPreparationInfo() = endLat = endLatitude, endLng = endLongitude, repeatDays = repeatDays, + transportType = transportType, ) private fun Schedule.toDepartureInfo() = @@ -228,4 +229,5 @@ private fun Schedule.toDepartureInfo() = endLat = endLatitude, endLng = endLongitude, repeatDays = repeatDays, + transportType = transportType, ) diff --git a/data/src/commonMain/kotlin/com/ondot/data/local/datasource/ScheduleLocalDataSourceImpl.kt b/data/src/commonMain/kotlin/com/ondot/data/local/datasource/ScheduleLocalDataSourceImpl.kt index 85f88c23..a4a562c1 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/local/datasource/ScheduleLocalDataSourceImpl.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/local/datasource/ScheduleLocalDataSourceImpl.kt @@ -89,6 +89,7 @@ class ScheduleLocalDataSourceImpl( preparationAlarm = item.preparationAlarm, departureAlarm = item.departureAlarm, hasActiveAlarm = item.hasActiveAlarm, + transportType = item.transportType.name, ) } diff --git a/data/src/commonMain/kotlin/com/ondot/data/local/db/Mappers.kt b/data/src/commonMain/kotlin/com/ondot/data/local/db/Mappers.kt index 6282e3c2..460f0c50 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/local/db/Mappers.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/local/db/Mappers.kt @@ -1,6 +1,7 @@ package com.ondot.data.local.db import com.dh.ondot.data.local.db.Schedule_entity +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.schedule.Schedule fun Schedule_entity.toDomain(): Schedule = @@ -17,6 +18,7 @@ fun Schedule_entity.toDomain(): Schedule = preparationAlarm = preparationAlarm, departureAlarm = departureAlarm, hasActiveAlarm = hasActiveAlarm, + transportType = TransportType.from(transportType), ) fun Schedule.toEntity() = @@ -33,4 +35,5 @@ fun Schedule.toEntity() = preparationAlarm = preparationAlarm, departureAlarm = departureAlarm, hasActiveAlarm = hasActiveAlarm, + transportType = transportType.name, ) diff --git a/data/src/commonMain/kotlin/com/ondot/data/mapper/ScheduleDetailResponseMapper.kt b/data/src/commonMain/kotlin/com/ondot/data/mapper/ScheduleDetailResponseMapper.kt index 43a9c913..e4ba47c0 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/mapper/ScheduleDetailResponseMapper.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/mapper/ScheduleDetailResponseMapper.kt @@ -1,6 +1,7 @@ package com.ondot.data.mapper import com.ondot.data.model.response.schedule.ScheduleDetailResponse +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.schedule.ScheduleDetail import com.ondot.network.base.Mapper @@ -16,6 +17,7 @@ object ScheduleDetailResponseMapper : Mapper { departureAlarm = AlarmResponseMapper.responseToModel(scheduleResponse.departureAlarm), hasActiveAlarm = scheduleResponse.hasActiveAlarm, preparationNote = scheduleResponse.preparationNote ?: "", + transportType = TransportType.from(scheduleResponse.transportType), ) } ?: emptyList(), ) diff --git a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleDetailResponse.kt b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleDetailResponse.kt index eac3b830..c17a6716 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleDetailResponse.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleDetailResponse.kt @@ -14,4 +14,5 @@ data class ScheduleDetailResponse( val arrivalPlace: AddressResponse = AddressResponse(), val preparationAlarm: AlarmResponse = AlarmResponse(), val departureAlarm: AlarmResponse = AlarmResponse(), + val transportType: String? = null, ) diff --git a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleResponse.kt b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleResponse.kt index fd10cdd6..c76c9f59 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleResponse.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/ScheduleResponse.kt @@ -19,4 +19,5 @@ data class ScheduleResponse( val departureAlarm: AlarmResponse = AlarmResponse(), val hasActiveAlarm: Boolean = false, val preparationNote: String? = null, + val transportType: String? = null, ) diff --git a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/mapper/ScheduleResponseMapper.kt b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/mapper/ScheduleResponseMapper.kt index 8d651c83..0a15cedc 100644 --- a/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/mapper/ScheduleResponseMapper.kt +++ b/data/src/commonMain/kotlin/com/ondot/data/model/response/schedule/mapper/ScheduleResponseMapper.kt @@ -6,6 +6,7 @@ import com.ondot.data.model.response.schedule.ScheduleResponse import com.ondot.data.model.response.schedule.TimetableEntryResponse import com.ondot.domain.model.enums.DayOfWeekKey import com.ondot.domain.model.enums.ScheduleType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.schedule.EverytimeValidateTimetable import com.ondot.domain.model.schedule.Schedule import com.ondot.domain.model.schedule.TimetableEntry @@ -44,6 +45,7 @@ fun ScheduleResponse.toDomain(): Schedule = endLongitude = endLongitude, endLatitude = endLatitude, preparationNote = preparationNote.orEmpty(), + transportType = TransportType.from(transportType), ) private fun String.toDayOfWeekKeyOrNull(): DayOfWeekKey? = runCatching { DayOfWeekKey.valueOf(this) }.getOrNull() diff --git a/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/1.sqm b/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/1.sqm new file mode 100644 index 00000000..4c8f406b --- /dev/null +++ b/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/1.sqm @@ -0,0 +1,2 @@ +ALTER TABLE schedule_entity +ADD COLUMN transportType TEXT NOT NULL DEFAULT 'PUBLIC_TRANSPORT'; diff --git a/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/ScheduleEntity.sq b/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/ScheduleEntity.sq index ebbbfce4..6d0f918d 100644 --- a/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/ScheduleEntity.sq +++ b/data/src/commonMain/sqldelight/com/dh/ondot/data/local/db/ScheduleEntity.sq @@ -15,7 +15,8 @@ CREATE TABLE schedule_entity ( appointmentAt TEXT NOT NULL, preparationAlarm TEXT AS Alarm NOT NULL, departureAlarm TEXT AS Alarm NOT NULL, - hasActiveAlarm INTEGER AS Boolean NOT NULL + hasActiveAlarm INTEGER AS Boolean NOT NULL, + transportType TEXT NOT NULL DEFAULT 'PUBLIC_TRANSPORT' ); CREATE INDEX IF NOT EXISTS idx_schedule_appointmentAt ON schedule_entity(appointmentAt); @@ -32,8 +33,8 @@ upsert: INSERT OR REPLACE INTO schedule_entity( scheduleId, startLongitude, startLatitude, endLongitude, endLatitude, scheduleTitle, isRepeat, repeatDays, appointmentAt, - preparationAlarm, departureAlarm, hasActiveAlarm -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + preparationAlarm, departureAlarm, hasActiveAlarm, transportType +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); deleteById: DELETE FROM schedule_entity WHERE scheduleId = ?; diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/model/enums/TransportType.kt b/domain/src/commonMain/kotlin/com/ondot/domain/model/enums/TransportType.kt index 8db3ebf6..59db8b23 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/model/enums/TransportType.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/model/enums/TransportType.kt @@ -3,4 +3,14 @@ package com.ondot.domain.model.enums enum class TransportType { PUBLIC_TRANSPORT, CAR, + + ; + + companion object { + fun from(value: String?): TransportType = + value + ?.let { + runCatching { valueOf(it.trim().uppercase()) }.getOrNull() + } ?: PUBLIC_TRANSPORT + } } diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/model/request/CreateScheduleRequest.kt b/domain/src/commonMain/kotlin/com/ondot/domain/model/request/CreateScheduleRequest.kt index d4c9aff6..954f8928 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/model/request/CreateScheduleRequest.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/model/request/CreateScheduleRequest.kt @@ -16,4 +16,5 @@ data class CreateScheduleRequest( val arrivalPlace: AddressInfo, val preparationAlarm: Alarm, val departureAlarm: Alarm, + val transportType: String, ) diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/Schedule.kt b/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/Schedule.kt index bb175527..c84a4684 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/Schedule.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/Schedule.kt @@ -2,6 +2,7 @@ package com.ondot.domain.model.schedule import com.ondot.domain.model.alarm.Alarm import com.ondot.domain.model.enums.ScheduleType +import com.ondot.domain.model.enums.TransportType data class Schedule( val scheduleId: Long = -1L, @@ -18,4 +19,5 @@ data class Schedule( val departureAlarm: Alarm = Alarm(), val hasActiveAlarm: Boolean = false, val preparationNote: String = "", + val transportType: TransportType = TransportType.PUBLIC_TRANSPORT, ) diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/ScheduleDetail.kt b/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/ScheduleDetail.kt index 9432bb80..71d5a6a4 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/ScheduleDetail.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/model/schedule/ScheduleDetail.kt @@ -1,6 +1,7 @@ package com.ondot.domain.model.schedule import com.ondot.domain.model.alarm.Alarm +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import kotlinx.serialization.Serializable @@ -14,4 +15,5 @@ data class ScheduleDetail( val arrivalPlace: AddressInfo = AddressInfo(), val preparationAlarm: Alarm = Alarm(), val departureAlarm: Alarm = Alarm(), + val transportType: TransportType = TransportType.PUBLIC_TRANSPORT, ) diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/model/ui/AlarmRingInfo.kt b/domain/src/commonMain/kotlin/com/ondot/domain/model/ui/AlarmRingInfo.kt index 356f09a2..1fbe6ebb 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/model/ui/AlarmRingInfo.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/model/ui/AlarmRingInfo.kt @@ -2,6 +2,7 @@ package com.ondot.domain.model.ui import com.ondot.domain.model.alarm.Alarm import com.ondot.domain.model.enums.AlarmType +import com.ondot.domain.model.enums.TransportType data class AlarmRingInfo( val alarm: Alarm = Alarm(), @@ -14,4 +15,5 @@ data class AlarmRingInfo( val endLat: Double = 0.0, val endLng: Double = 0.0, val repeatDays: List = emptyList(), + val transportType: TransportType = TransportType.PUBLIC_TRANSPORT, ) diff --git a/domain/src/commonMain/kotlin/com/ondot/domain/service/DirectionsOpener.kt b/domain/src/commonMain/kotlin/com/ondot/domain/service/DirectionsOpener.kt index 579d9943..ee1fc161 100644 --- a/domain/src/commonMain/kotlin/com/ondot/domain/service/DirectionsOpener.kt +++ b/domain/src/commonMain/kotlin/com/ondot/domain/service/DirectionsOpener.kt @@ -1,6 +1,7 @@ package com.ondot.domain.service import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType interface DirectionsOpener { fun openDirections( @@ -11,5 +12,6 @@ interface DirectionsOpener { provider: MapProvider, startName: String, endName: String, + transportType: TransportType, ) } diff --git a/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/FakeScheduleRepository.kt b/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/FakeScheduleRepository.kt index 6de7e4e4..99a4ba55 100644 --- a/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/FakeScheduleRepository.kt +++ b/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/FakeScheduleRepository.kt @@ -2,6 +2,7 @@ package com.ondot.testing.fake import com.ondot.domain.model.alarm.Alarm import com.ondot.domain.model.command.CreateEverytimeScheduleCommand +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.request.CreateScheduleRequest import com.ondot.domain.model.request.ScheduleAlarmRequest import com.ondot.domain.model.request.ToggleAlarmRequest @@ -64,6 +65,7 @@ class FakeScheduleRepository : ScheduleRepository { hasActiveAlarm = false, departureAlarm = request.departureAlarm, preparationAlarm = request.preparationAlarm, + transportType = TransportType.from(request.transportType), ) scheduleMap[newId] = schedule @@ -168,6 +170,7 @@ class FakeScheduleRepository : ScheduleRepository { hasActiveAlarm = false, departureAlarm = request.departureAlarm, preparationAlarm = request.preparationAlarm, + transportType = TransportType.from(request.transportType), ) scheduleMap[newId] = schedule diff --git a/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/util/FakeDirectionsOpener.kt b/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/util/FakeDirectionsOpener.kt index 1f0a2a15..040090af 100644 --- a/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/util/FakeDirectionsOpener.kt +++ b/domain/testing/src/commonMain/kotlin/com/ondot/testing/fake/util/FakeDirectionsOpener.kt @@ -1,6 +1,7 @@ package com.ondot.testing.fake.util import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.service.DirectionsOpener class FakeDirectionsOpener : DirectionsOpener { @@ -10,6 +11,7 @@ class FakeDirectionsOpener : DirectionsOpener { val endLat: Double, val endLng: Double, val provider: MapProvider, + val transportType: TransportType, ) val calls = mutableListOf() @@ -22,6 +24,7 @@ class FakeDirectionsOpener : DirectionsOpener { provider: MapProvider, startName: String, endName: String, + transportType: TransportType, ) { calls += Call( @@ -30,6 +33,7 @@ class FakeDirectionsOpener : DirectionsOpener { endLat = endLat, endLng = endLng, provider = provider, + transportType = transportType, ) } } diff --git a/feature/alarm/src/commonMain/kotlin/com/ondot/alarm/AlarmViewModel.kt b/feature/alarm/src/commonMain/kotlin/com/ondot/alarm/AlarmViewModel.kt index 6a949063..2b01c236 100644 --- a/feature/alarm/src/commonMain/kotlin/com/ondot/alarm/AlarmViewModel.kt +++ b/feature/alarm/src/commonMain/kotlin/com/ondot/alarm/AlarmViewModel.kt @@ -154,6 +154,7 @@ class AlarmViewModel( startName = "출발지", endName = "도착지", provider = uiState.value.mapProvider, + transportType = schedule.transportType, ) } @@ -205,6 +206,7 @@ class AlarmViewModel( startLng = nextSchedule.startLongitude, endLat = nextSchedule.endLatitude, endLng = nextSchedule.endLongitude, + transportType = nextSchedule.transportType, ) alarmScheduler.scheduleAlarm(alarmInfo, uiState.value.mapProvider) @@ -221,6 +223,7 @@ class AlarmViewModel( startLng = nextSchedule.startLongitude, endLat = nextSchedule.endLatitude, endLng = nextSchedule.endLongitude, + transportType = nextSchedule.transportType, ) alarmScheduler.scheduleAlarm(alarmInfo, uiState.value.mapProvider) } @@ -282,6 +285,7 @@ class AlarmViewModel( endLng = newSchedule.endLongitude, scheduleTitle = newSchedule.scheduleTitle, appointmentAt = newSchedule.appointmentAt, + transportType = newSchedule.transportType, ), mapProvider = uiState.value.mapProvider, ) diff --git a/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleScreen.kt b/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleScreen.kt index 7e248a57..8a30de21 100644 --- a/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleScreen.kt +++ b/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -91,7 +90,6 @@ fun EditScheduleScreen( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val focusRequester = remember { FocusRequester() } - val interactionSource = remember { MutableInteractionSource() } LaunchedEffect(Unit) { delay(200) @@ -112,7 +110,6 @@ fun EditScheduleScreen( if (uiState.isInitialized) { EditScheduleContent( uiState = uiState, - interactionSource = interactionSource, focusRequester = focusRequester, onClickClose = popScreen, onValueChanged = viewModel::updateScheduleTitle, @@ -136,7 +133,6 @@ fun EditScheduleScreen( @Composable fun EditScheduleContent( uiState: EditScheduleUiState, - interactionSource: MutableInteractionSource, focusRequester: FocusRequester, onClickClose: () -> Unit, onValueChanged: (String) -> Unit, @@ -204,7 +200,6 @@ fun EditScheduleContent( repeatDays = uiState.schedule.repeatDays, date = uiState.selectedDate, time = appointmentDate, - interactionSource = interactionSource, onClickDate = onShowDateBottomSheet, onClickTime = { onShowTimeBottomSheet(TimeType.APPOINTMENT) }, ) @@ -232,7 +227,6 @@ fun EditScheduleContent( info = uiState.schedule.preparationAlarm, type = AlarmType.Preparation, scheduleDate = uiState.schedule.appointmentAt, - interactionSource = interactionSource, onClick = { onShowTimeBottomSheet(TimeType.PREPARATION) }, onToggleSwitch = onToggleSwitch, ) @@ -243,7 +237,6 @@ fun EditScheduleContent( info = uiState.schedule.departureAlarm, type = AlarmType.Departure, scheduleDate = uiState.schedule.appointmentAt, - interactionSource = interactionSource, onClick = { onShowTimeBottomSheet(TimeType.DEPARTURE) }, ) diff --git a/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleViewModel.kt b/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleViewModel.kt index baa40170..16463a53 100644 --- a/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleViewModel.kt +++ b/feature/edit/src/commonMain/kotlin/com/ondot/edit/EditScheduleViewModel.kt @@ -332,5 +332,6 @@ class EditScheduleViewModel( endLatitude = arrivalPlace.latitude, endLongitude = arrivalPlace.longitude, hasActiveAlarm = departureAlarm.enabled || preparationAlarm.enabled, + transportType = transportType, ) } diff --git a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeIntent.kt b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeIntent.kt index 7b2b54f6..efd93e19 100644 --- a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeIntent.kt +++ b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeIntent.kt @@ -1,6 +1,7 @@ package com.ondot.everytime.contract import com.ondot.domain.model.enums.RouterType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.PlaceHistory import com.ondot.ui.base.mvi.Intent @@ -41,4 +42,8 @@ sealed interface EverytimeIntent : Intent { ) : EverytimeIntent data object InitPlaceHistory : EverytimeIntent + + data class UpdateTransportType( + val type: TransportType, + ) : EverytimeIntent } diff --git a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeViewModel.kt b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeViewModel.kt index 4c9722d6..3d8797e7 100644 --- a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeViewModel.kt +++ b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/contract/EverytimeViewModel.kt @@ -77,6 +77,7 @@ class EverytimeViewModel( is EverytimeIntent.UpdateRouteInput -> updateRouteInput(intent.input) is EverytimeIntent.SetSelectedPlace -> setSelectedPlace(intent.place) EverytimeIntent.InitPlaceHistory -> fetchHistory() + is EverytimeIntent.UpdateTransportType -> setTransportType(intent.type) } } @@ -163,7 +164,7 @@ class EverytimeViewModel( selectedLectures = selectedLectures, departurePlace = departure, arrivalPlace = arrival, - transportType = TransportType.PUBLIC_TRANSPORT, + transportType = currentState.placePickerState.selectedTransportType, ) launchResult( @@ -306,6 +307,11 @@ class EverytimeViewModel( } } + // 교통수단 선택 + private fun setTransportType(type: TransportType) { + reduce { copy(placePickerState = currentState.placePickerState.copy(selectedTransportType = type)) } + } + // 가장 최근 활성화 된 텍스트 필드 타입 저장 private fun setLastFocusedTextField(type: RouterType) { reduce { copy(placePickerState = currentState.placePickerState.copy(lastFocusedTextField = type)) } diff --git a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/placepicker/EverytimePlacePickerScreen.kt b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/placepicker/EverytimePlacePickerScreen.kt index 62e07afe..83ae575a 100644 --- a/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/placepicker/EverytimePlacePickerScreen.kt +++ b/feature/everytime/src/commonMain/kotlin/com/ondot/everytime/placepicker/EverytimePlacePickerScreen.kt @@ -71,6 +71,7 @@ fun EverytimePlacePickerRoute( }, onDeleteHistory = { viewModel.dispatch(EverytimeIntent.DeleteHistory(it)) }, onToggleCheckBox = { viewModel.dispatch(EverytimeIntent.ToggleCheckBox) }, + onTransportTypeChanged = { viewModel.dispatch(EverytimeIntent.UpdateTransportType(it)) }, onNext = { viewModel.dispatch(EverytimeIntent.CreateSchedule) }, popScreen = popScreen, ) diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/GeneralScheduleViewModel.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/GeneralScheduleViewModel.kt index a33e149c..9824281c 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/GeneralScheduleViewModel.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/GeneralScheduleViewModel.kt @@ -9,6 +9,7 @@ import com.dh.ondot.presentation.ui.theme.ERROR_GET_SCHEDULE_ALARMS import com.dh.ondot.presentation.ui.theme.ERROR_SEARCH_PLACE import com.ondot.domain.model.enums.RouterType import com.ondot.domain.model.enums.ToastType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.HomeAddressInfo import com.ondot.domain.model.member.PlaceHistory @@ -464,6 +465,7 @@ class GeneralScheduleViewModel( appointmentAt = appointmentAt, preparationAlarm = uiState.value.preparationAlarm, departureAlarm = uiState.value.departureAlarm, + transportType = TransportType.PUBLIC_TRANSPORT.name, ) viewModelScope.launch { diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleIntent.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleIntent.kt index 2c655a42..7c054d47 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleIntent.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleIntent.kt @@ -1,6 +1,7 @@ package com.ondot.general.contract import com.ondot.domain.model.enums.RouterType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.PlaceHistory import com.ondot.ui.base.mvi.Intent @@ -86,4 +87,8 @@ sealed interface GeneralScheduleIntent : Intent { val isMedicationRequired: Boolean, val preparationNote: String, ) : GeneralScheduleIntent + + data class UpdateTransportType( + val type: TransportType, + ) : GeneralScheduleIntent } diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleState.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleState.kt index de560545..3a3bfb91 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleState.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleState.kt @@ -48,14 +48,6 @@ data class GeneralScheduleState( val isPlacePickerButtonEnabled: Boolean get() = placePickerState.selectedDeparturePlace != null && placePickerState.selectedArrivalPlace != null - val isCurrentStepButtonEnabled: Boolean - get() = - when (currentStep) { - 1 -> isRepeatStepButtonEnabled - 2 -> isPlacePickerButtonEnabled - else -> false - } - companion object { fun formattedDate(date: String) = DateTimeFormatter.formatKoreanDateMonthDay(date) } diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleViewModel.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleViewModel.kt index 80b99a6e..45d2ef2b 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleViewModel.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/contract/GeneralScheduleViewModel.kt @@ -8,6 +8,7 @@ import com.dh.ondot.presentation.ui.theme.ERROR_GET_SCHEDULE_ALARMS import com.dh.ondot.presentation.ui.theme.ERROR_SEARCH_PLACE import com.ondot.domain.model.enums.RouterType import com.ondot.domain.model.enums.ToastType +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.member.AddressInfo import com.ondot.domain.model.member.HomeAddressInfo import com.ondot.domain.model.member.PlaceHistory @@ -97,11 +98,18 @@ class GeneralScheduleViewModel( GeneralScheduleIntent.TogglePreparationAlarm -> togglePreparationAlarm() is GeneralScheduleIntent.SetBottomSheetVisible -> setBottomSheetVisible(intent.visible) is GeneralScheduleIntent.CreateSchedule -> createSchedule(intent.isMedicationRequired, intent.preparationNote) + is GeneralScheduleIntent.UpdateTransportType -> setTransportType(intent.type) } } private fun initStep() { - reduce { copy(totalStep = 2, currentStep = 1) } + reduce { + copy( + totalStep = 2, + currentStep = 1, + placePickerState = placePickerState.copy(steps = Pair(1, 2)), + ) + } } private fun toggleRepeat(newValue: Boolean) { @@ -411,7 +419,7 @@ class GeneralScheduleViewModel( } @OptIn(ExperimentalTime::class) - private suspend fun createSchedule( + private fun createSchedule( isMedicationRequired: Boolean, preparationNote: String, ) { @@ -438,6 +446,7 @@ class GeneralScheduleViewModel( appointmentAt = appointmentAt, preparationAlarm = currentState.preparationAlarm, departureAlarm = currentState.departureAlarm, + transportType = currentState.placePickerState.selectedTransportType.name, ) launchResult( @@ -463,11 +472,24 @@ class GeneralScheduleViewModel( reduce { copy(showBottomSheet = visible) } } + private fun setTransportType(type: TransportType) { + reduce { + copy( + placePickerState = placePickerState.copy(selectedTransportType = type), + ) + } + } + private suspend fun clickNext() { when (currentState.currentStep) { 1 -> { - reduce { copy(currentStep = currentStep + 1) } emitEffect(GeneralScheduleSideEffect.NavigateToPlacePicker) + reduce { + copy( + currentStep = currentStep + 1, + placePickerState = placePickerState.copy(steps = Pair(currentStep + 1, totalStep)), + ) + } } 2 -> { diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/place/PlacePickerScreen.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/place/PlacePickerScreen.kt index c17225f0..bf0d4c78 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/place/PlacePickerScreen.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/place/PlacePickerScreen.kt @@ -78,6 +78,7 @@ fun PlacePickerRoute( }, onDeleteHistory = viewModel::deletePlaceHistory, onToggleCheckBox = viewModel::onClickCheckBox, + onTransportTypeChanged = {}, onNext = viewModel::onClickNextButton, popScreen = { viewModel.onClickBackButton() diff --git a/feature/general/src/commonMain/kotlin/com/ondot/general/ui/place/PlacePickerRoute.kt b/feature/general/src/commonMain/kotlin/com/ondot/general/ui/place/PlacePickerRoute.kt index 1911f8bf..c06ed56b 100644 --- a/feature/general/src/commonMain/kotlin/com/ondot/general/ui/place/PlacePickerRoute.kt +++ b/feature/general/src/commonMain/kotlin/com/ondot/general/ui/place/PlacePickerRoute.kt @@ -79,6 +79,7 @@ fun PlacePickerRoute( }, onDeleteHistory = { viewModel.dispatch(GeneralScheduleIntent.DeleteHistory(it)) }, onToggleCheckBox = { viewModel.dispatch(GeneralScheduleIntent.ToggleHomeDeparture) }, + onTransportTypeChanged = { viewModel.dispatch(GeneralScheduleIntent.UpdateTransportType(it)) }, onNext = { viewModel.dispatch(GeneralScheduleIntent.ClickNext) }, popScreen = { viewModel.dispatch(GeneralScheduleIntent.ClickBack) diff --git a/feature/main/src/commonMain/kotlin/com/ondot/main/home/HomeViewModel.kt b/feature/main/src/commonMain/kotlin/com/ondot/main/home/HomeViewModel.kt index e6ec074e..ea923f2c 100644 --- a/feature/main/src/commonMain/kotlin/com/ondot/main/home/HomeViewModel.kt +++ b/feature/main/src/commonMain/kotlin/com/ondot/main/home/HomeViewModel.kt @@ -312,6 +312,7 @@ class HomeViewModel( provider = mapProvider, startName = "출발지", endName = "도착지", + transportType = schedule.transportType, ) } diff --git a/feature/main/src/commonTest/kotlin/com/ondot/main/home/HomeViewModelTest.kt b/feature/main/src/commonTest/kotlin/com/ondot/main/home/HomeViewModelTest.kt index 3fccb288..3a1d0ebe 100644 --- a/feature/main/src/commonTest/kotlin/com/ondot/main/home/HomeViewModelTest.kt +++ b/feature/main/src/commonTest/kotlin/com/ondot/main/home/HomeViewModelTest.kt @@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger import com.ondot.domain.model.alarm.Alarm import com.ondot.domain.model.enums.AlarmType import com.ondot.domain.model.enums.MapProvider +import com.ondot.domain.model.enums.TransportType import com.ondot.domain.model.schedule.Schedule import com.ondot.domain.repository.AlarmRepository import com.ondot.testing.fake.FakeAlarmRepository @@ -434,6 +435,7 @@ class HomeViewModelTest { assertEquals(s1.endLatitude, call.endLat) assertEquals(s1.endLongitude, call.endLng) assertEquals(MapProvider.NAVER, call.provider) + assertEquals(TransportType.PUBLIC_TRANSPORT, call.transportType) } /**---------------------------------------------더미 데이터 생성 유틸 메서드----------------------------------------------*/ diff --git a/gradle.properties b/gradle.properties index b08963f6..ac6f9ef9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,5 +21,5 @@ target.sdk=36 compile.sdk=36 # Version -version.code=38 -version.name=1.3.1 \ No newline at end of file +version.code=39 +version.name=1.4.0 \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index cf55ae24..d13a4452 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -468,7 +468,7 @@ CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 38; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -491,7 +491,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.1; + MARKETING_VERSION = 1.4.0; ONLY_ACTIVE_ARCH = NO; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.dh.ondot.iosApp; @@ -520,7 +520,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 38; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Y2Q4TFA96S; @@ -545,7 +545,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.1; + MARKETING_VERSION = 1.4.0; ONLY_ACTIVE_ARCH = NO; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.dh.ondot.iosApp; diff --git a/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.h b/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.h index 2f811b55..84594b9f 100644 --- a/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.h +++ b/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN endLat:(NSNumber * _Nullable)endLat endLng:(NSNumber * _Nullable)endLng mapProvider:(NSString * _Nullable)mapProvider + transportType:(NSString * _Nullable)transportType completion:(void(^)(NSString * _Nullable alarmUUID, NSString * _Nullable errorMsg))completion; diff --git a/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.m b/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.m index d74e0496..ce304777 100644 --- a/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.m +++ b/iosApp/iosApp/AlarmKitBridge/ONDAlarmKit.m @@ -51,6 +51,7 @@ + (void)scheduleCalendarWithId:(NSString * _Nullable)alarmUUID endLat:(NSNumber * _Nullable)endLat endLng:(NSNumber * _Nullable)endLng mapProvider:(NSString * _Nullable)mapProvider + transportType:(NSString * _Nullable)transportType completion:(void(^)(NSString * _Nullable, NSString * _Nullable))completion { [AlarmKitBridgeShim scheduleCalendarWithId:alarmUUID @@ -67,6 +68,7 @@ + (void)scheduleCalendarWithId:(NSString * _Nullable)alarmUUID endLat:endLat endLng:endLng mapProvider:mapProvider + transportType:transportType completion:completion]; } diff --git a/iosApp/iosApp/Util/AlarmKitBridgeShim.swift b/iosApp/iosApp/Util/AlarmKitBridgeShim.swift index ed294dba..3ca230f4 100644 --- a/iosApp/iosApp/Util/AlarmKitBridgeShim.swift +++ b/iosApp/iosApp/Util/AlarmKitBridgeShim.swift @@ -55,6 +55,7 @@ public final class AlarmKitBridgeShim: NSObject { startLat: NSNumber?, startLng: NSNumber?, endLat: NSNumber?, endLng: NSNumber?, mapProvider: String, + transportType: String, completion: @escaping (String?, String?) -> Void ) { Task { @MainActor in @@ -71,6 +72,7 @@ public final class AlarmKitBridgeShim: NSObject { startLat: startLat, startLng: startLng, endLat: endLat, endLng: endLng, mapProvider: mapProvider, + transportType: transportType, completion ) } diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index bc9e4066..153b9e6d 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -58,6 +58,7 @@ private func consumePendingDirectionsAndOpen() { let sname = (payload["sname"] as? String) ?? "출발지" let ename = (payload["ename"] as? String) ?? "도착지" let providerStr = (payload["provider"] as? String)?.lowercased() ?? "kakao" + let transportTypeStr = (payload["transportType"] as? String)?.lowercased() ?? "public_transport" guard let sLat = slat, let sLng = slng, @@ -71,6 +72,13 @@ private func consumePendingDirectionsAndOpen() { default: return .kakao } }() + + let transportType: composeApp.DomainTransportType = { + switch transportTypeStr { + case "car": return .car + default: return .publicTransport + } + }() composeApp.TriggeredAlarmManager().recordTriggeredAlarm( scheduleId: Int64(scheduleId), @@ -82,6 +90,7 @@ private func consumePendingDirectionsAndOpen() { startLat: sLat, startLng: sLng, endLat: eLat, endLng: eLng, provider: provider, - startName: sname, endName: ename + startName: sname, endName: ename, + transportType: transportType ) }