diff --git a/build.gradle.kts b/build.gradle.kts
index f00a42392..b153960b9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,8 +7,8 @@ buildscript {
}
extra.apply {
- set("versionName", "4.1.6")
- set("versionCode", 40106)
+ set("versionName", "4.2.0")
+ set("versionCode", 40200)
// 코인 버전 관리
set("versionBusinessName", "1.0.1")
diff --git a/core/analytics/.gitignore b/core/analytics/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/core/analytics/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts
new file mode 100644
index 000000000..c3bfc5ad6
--- /dev/null
+++ b/core/analytics/build.gradle.kts
@@ -0,0 +1,29 @@
+plugins {
+ alias(libs.plugins.koin.library)
+}
+
+android {
+ namespace = "in.koreatech.koin.core.analytics"
+
+ buildTypes {
+ getByName("debug") {
+ buildConfigField("Boolean", "IS_DEBUG", "true")
+ }
+
+ getByName("release") {
+ buildConfigField("Boolean", "IS_DEBUG", "false")
+ }
+ }
+}
+
+dependencies {
+
+ implementation(libs.core.ktx)
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.analytics)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.ext.junit)
+ androidTestImplementation(libs.espresso.core)
+}
\ No newline at end of file
diff --git a/core/analytics/consumer-rules.pro b/core/analytics/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/core/analytics/proguard-rules.pro b/core/analytics/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/core/analytics/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/analytics/src/androidTest/java/in/koreatech/koin/core/analytics/ExampleInstrumentedTest.kt b/core/analytics/src/androidTest/java/in/koreatech/koin/core/analytics/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..4e5561bd9
--- /dev/null
+++ b/core/analytics/src/androidTest/java/in/koreatech/koin/core/analytics/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package `in`.koreatech.koin.core.analytics
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("in.koreatech.koin.core.analytics.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/core/analytics/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt b/core/analytics/src/main/java/in/koreatech/koin/core/analytics/AnalyticsConstant.kt
similarity index 97%
rename from core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt
rename to core/analytics/src/main/java/in/koreatech/koin/core/analytics/AnalyticsConstant.kt
index 3ed94f8e3..9f65ee2b0 100644
--- a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt
+++ b/core/analytics/src/main/java/in/koreatech/koin/core/analytics/AnalyticsConstant.kt
@@ -1,4 +1,4 @@
-package `in`.koreatech.koin.core.constant
+package `in`.koreatech.koin.core.analytics
object AnalyticsConstant {
@@ -18,8 +18,8 @@ object AnalyticsConstant {
const val CAFETERIA_INFO = "cafeteria_info"
const val HAMBURGER = "hamburger"
const val HAMBURGER_SHOP = HAMBURGER
- const val HAMBURGER_DINING = "${HAMBURGER}"
- const val HAMBURGER_BUS = "${HAMBURGER}"
+ const val HAMBURGER_DINING = "$HAMBURGER"
+ const val HAMBURGER_BUS = "$HAMBURGER"
const val MAIN_MENU_MOVEDETAILVIEW = "main_menu_moveDetailView"
const val MAIN_MENU_CORNER = "main_menu_corner"
const val MENU_TIME = "menu_time"
diff --git a/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt b/core/analytics/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt
similarity index 62%
rename from core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt
rename to core/analytics/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt
index 1df10adda..8b46e0aa9 100644
--- a/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt
+++ b/core/analytics/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt
@@ -1,11 +1,8 @@
package `in`.koreatech.koin.core.analytics
-import android.util.Log
-import com.google.firebase.analytics.ktx.analytics
-import com.google.firebase.analytics.ktx.logEvent
-import com.google.firebase.ktx.Firebase
-import `in`.koreatech.koin.core.BuildConfig
-import `in`.koreatech.koin.core.analytics.EventLogger.logEvent
+import com.google.firebase.analytics.analytics
+import com.google.firebase.analytics.logEvent
+import com.google.firebase.Firebase
object EventLogger {
@@ -15,23 +12,31 @@ object EventLogger {
/**
* 클릭 이벤트 로깅
- * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @param extras: 추가 이벤트 값
+ * @param action 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
*/
fun logClickEvent(action: EventAction, label: String, value: String, vararg extras: EventExtra) {
logEvent(action, EventCategory.CLICK, label, value, *extras)
}
-
+ /**
+ * CAPMUS 클릭 이벤트 로깅
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
+ */
+ fun logCampusClickEvent(label: String, value: String, vararg extras: EventExtra) {
+ logClickEvent(EventAction.CAMPUS, label, value, *extras)
+ }
/**
* 스크롤 이벤트 로깅
- * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @param extras: 추가 이벤트 값
+ * @param action 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
*/
fun logScrollEvent(action: EventAction, label: String, value: String, vararg extras: EventExtra) {
logEvent(action, EventCategory.SCROLL, label, value, *extras)
@@ -39,10 +44,10 @@ object EventLogger {
/**
* 하단 뒤로가기 이벤트 로깅
- * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @param extras: 추가 이벤트 값
+ * @param action 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
*/
fun logSwipeEvent(action: EventAction, label: String, value: String, vararg extras: EventExtra) {
logEvent(action, EventCategory.SWIPE, label, value, *extras)
@@ -50,10 +55,10 @@ object EventLogger {
/**
* 푸시알림 접속 이벤트 로깅
- * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @param extras: 추가 이벤트 값
+ * @param action 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
*/
fun logNotificationEvent(action: EventAction, label: String, value: String, vararg extras: EventExtra) {
logEvent(action, EventCategory.NOTIFICATION, label, value, *extras)
@@ -61,20 +66,23 @@ object EventLogger {
/**
* AB테스트 이벤트 로깅
- * @param category: 이벤트 종류
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
+ * @param category 이벤트 종류
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
*/
fun logABTestEvent(category: String, label: String, value: String) {
logCustomEvent(EventAction.ABTEST.value, category, label, value)
}
/**
- * @param action: 커스텀 이벤트 발생(EventAction 이외에 action)
- * @param category: 커스텀 이벤트 종류(EventCategory 이외에 category)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @sample logEvent("force_update", "page_view", "forced_update_page_view", "v4.0.0")
+ * @param action 커스텀 이벤트 발생(EventAction 이외에 action)
+ * @param category 커스텀 이벤트 종류(EventCategory 이외에 category)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ *
+ * ```
+ * logEvent("force_update", "page_view", "forced_update_page_view", "v4.0.0")
+ * ```
*/
fun logCustomEvent(action: String, category: String, label: String, value: String) {
if (BuildConfig.IS_DEBUG) {
@@ -94,11 +102,15 @@ object EventLogger {
}
}
/**
- * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
- * @param category: 이벤트 종류(click, scroll, ...)
- * @param label: 이벤트 소분류
- * @param value: 이벤트 값
- * @sample logEvent("BUSINESS", "click", "main_shop_categories", "전체보기")
+ * @param action 이벤트 발생 도메인(BUSINESS, CAMPUS, USER)
+ * @param category 이벤트 종류(click, scroll, ...)
+ * @param label 이벤트 소분류
+ * @param value 이벤트 값
+ * @param extras 추가 이벤트 값
+ *
+ * ```
+ * logEvent(EventAction.CAMPUS, EventCategory.CLICK, "main_shop_categories", "전체보기")
+ * ```
*/
private fun logEvent(action: EventAction, category: EventCategory, label: String, value: String, vararg extras: EventExtra) {
if (BuildConfig.IS_DEBUG) {
diff --git a/core/src/main/java/in/koreatech/koin/core/analytics/EventUtils.kt b/core/analytics/src/main/java/in/koreatech/koin/core/analytics/EventUtils.kt
similarity index 100%
rename from core/src/main/java/in/koreatech/koin/core/analytics/EventUtils.kt
rename to core/analytics/src/main/java/in/koreatech/koin/core/analytics/EventUtils.kt
diff --git a/core/analytics/src/test/java/in/koreatech/koin/core/analytics/ExampleUnitTest.kt b/core/analytics/src/test/java/in/koreatech/koin/core/analytics/ExampleUnitTest.kt
new file mode 100644
index 000000000..a08d16ef1
--- /dev/null
+++ b/core/analytics/src/test/java/in/koreatech/koin/core/analytics/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package `in`.koreatech.koin.core.analytics
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/chip/TextChip.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/chip/TextChip.kt
index 639861acb..3ed0835aa 100644
--- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/chip/TextChip.kt
+++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/chip/TextChip.kt
@@ -30,8 +30,8 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
*/
@Composable
fun TextChip(
- modifier: Modifier = Modifier,
title: String,
+ modifier: Modifier = Modifier,
isSelected: Boolean = false,
shape: Shape = RoundedCornerShape(50),
showClickRipple: Boolean = true,
diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/util/ComposeTextExtensions.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/util/ComposeTextExtensions.kt
new file mode 100644
index 000000000..6f2256659
--- /dev/null
+++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/util/ComposeTextExtensions.kt
@@ -0,0 +1,21 @@
+package `in`.koreatech.koin.core.designsystem.util
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.unit.Dp
+
+/**
+ * TextStyle에 대한 한글 텍스트 높이를 Dp로 반환
+ */
+@Composable
+fun TextStyle.getMeasuredKoreanHeightDp(): Dp {
+ return with(rememberTextMeasurer()) {
+ with(LocalDensity.current) {
+ measure(
+ "가", this@getMeasuredKoreanHeightDp
+ ).size.height.toDp()
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/in/koreatech/koin/core/activity/WebViewActivity.kt b/core/src/main/java/in/koreatech/koin/core/activity/WebViewActivity.kt
index 536426432..ca21d7216 100644
--- a/core/src/main/java/in/koreatech/koin/core/activity/WebViewActivity.kt
+++ b/core/src/main/java/in/koreatech/koin/core/activity/WebViewActivity.kt
@@ -47,7 +47,6 @@ class WebViewActivity : ActivityBase(R.layout.activity_webview) {
@SuppressLint("SetJavaScriptEnabled")
private fun init(title: String?, url: String?) {
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
setTitle(title)
binding.webView.apply {
diff --git a/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java b/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java
index f8ca0a312..8e0c82b21 100644
--- a/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java
+++ b/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java
@@ -12,17 +12,11 @@
import android.view.View;
import android.widget.TextView;
-
import android.util.AttributeSet;
-
import in.koreatech.koin.core.R;
-import in.koreatech.koin.core.analytics.EventAction;
-import in.koreatech.koin.core.analytics.EventLogger;
-import in.koreatech.koin.core.constant.AnalyticsConstant;
import in.koreatech.koin.core.util.FontManager;
-
public class AppBarBase extends AppBarLayout {
public AppBarLayout background;
public TextView leftButton;
diff --git a/core/src/main/res/layout/base_navigation_drawer_right.xml b/core/src/main/res/layout/base_navigation_drawer_right.xml
index efbaf73d1..94eb8169c 100644
--- a/core/src/main/res/layout/base_navigation_drawer_right.xml
+++ b/core/src/main/res/layout/base_navigation_drawer_right.xml
@@ -13,7 +13,8 @@
+ android:layout_height="match_parent"
+ android:paddingBottom="20dp">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toBottomOf="@id/navi_item_login_or_logout">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
주변 상점
버스 / 교통
+ 버스 시간표
+ 교통편 조회하기
식단
복덕방
시간표
diff --git a/data/src/main/java/in/koreatech/koin/data/api/BusApi.kt b/data/src/main/java/in/koreatech/koin/data/api/BusApi.kt
index 05ac9f281..26084aa1b 100644
--- a/data/src/main/java/in/koreatech/koin/data/api/BusApi.kt
+++ b/data/src/main/java/in/koreatech/koin/data/api/BusApi.kt
@@ -1,45 +1,45 @@
package `in`.koreatech.koin.data.api
-import `in`.koreatech.koin.data.constant.URLConstant
-import `in`.koreatech.koin.data.response.bus.*
+import `in`.koreatech.koin.data.response.bus.BusNoticeResponse
+import `in`.koreatech.koin.data.response.bus.BusSearchResultWrapperResponse
+import `in`.koreatech.koin.data.response.bus.CityTimetableResponse
+import `in`.koreatech.koin.data.response.bus.ExpressTimetableResponse
+import `in`.koreatech.koin.data.response.bus.ShuttleCoursesResponse
+import `in`.koreatech.koin.data.response.bus.ShuttleTimetableResponse
import retrofit2.http.GET
+import retrofit2.http.Path
import retrofit2.http.Query
interface BusApi {
- @GET(URLConstant.BUS.COURSES)
- suspend fun getBusCourses(): List
-
- @GET(URLConstant.BUS.TIMETABLE_V2)
- suspend fun getShuttleBusTimetable(
- @Query("bus_type") busType: String, //shuttle, commuting
- @Query("direction") busDirection: String, //to(등교), from(하교)
- @Query("region") region: String //courses의 region
- ) : ShuttleBusTimetableResponse
-
- @GET(URLConstant.BUS.TIMETABLE_V2 + "?bus_type=express")
- suspend fun getExpressBusTimetable(
- @Query("direction") busDirection: String, //to(등교), from(하교)
- @Query("region") region: String //courses의 region
- ) : ExpressBusTimetableResponse
-
- @GET(URLConstant.BUS.CITY)
- suspend fun getCityBusTimetable(
+
+ @GET("bus/notice")
+ suspend fun fetchBusNotice(): BusNoticeResponse
+
+ @GET("bus/timetable/shuttle/{id}")
+ suspend fun fetchShuttleTimetable(
+ @Path("id") id: String
+ ): ShuttleTimetableResponse
+
+ @GET("bus/courses/shuttle")
+ suspend fun fetchShuttleCourses(): ShuttleCoursesResponse
+
+ @GET("bus/timetable/v2?bus_type=EXPRESS®ion=null")
+ suspend fun fetchExpressTimetable(
+ @Query("direction") direction: String,
+ ): ExpressTimetableResponse
+
+ @GET("bus/timetable/city")
+ suspend fun fetchCityTimetable(
@Query("bus_number") busNumber: Int,
- @Query("direction") direction: String
- ): CityBusTimetableResponse
-
- @GET(URLConstant.BUS.BUS)
- suspend fun getBus(
- @Query("bus_type") busType: String, //shuttle, commuting, express, city
- @Query("depart") departure: String, //koreatech, station, terminal
- @Query("arrival") arrival: String //koreatech, station, terminal
- ) : BusResponse
-
- @GET(URLConstant.BUS.SEARCH)
- suspend fun searchBus(
- @Query("date") date: String, // yyyy-MM-dd
- @Query("time") time: String, // HH:mm
- @Query("depart") departure: String, //koreatech, station, terminal
- @Query("arrival") arrival: String //koreatech, station, terminal
- ) : List
+ @Query("direction") direction: String,
+ ): CityTimetableResponse
+
+ @GET("bus/route")
+ suspend fun fetchBusSearchResult(
+ @Query("date") date: String,
+ @Query("time") time: String,
+ @Query("bus_type") busType: String,
+ @Query("depart") departure: String,
+ @Query("arrival") arrival: String
+ ): BusSearchResultWrapperResponse
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt b/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt
index 83bcb494b..7b2672706 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/datastore/DataStoreModule.kt
@@ -10,6 +10,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import `in`.koreatech.koin.data.source.datastore.ArticleDataStore
+import `in`.koreatech.koin.data.source.datastore.BusDataStore
import `in`.koreatech.koin.data.source.datastore.TimetableDataStore
import javax.inject.Singleton
@@ -25,6 +26,10 @@ object DataStoreModule {
name = "timetables"
)
+ private val Context.busDataStore: DataStore by preferencesDataStore(
+ name = "bus.ds"
+ )
+
@Provides
@Singleton
fun provideArticleDataStore(
@@ -40,4 +45,12 @@ object DataStoreModule {
): TimetableDataStore {
return TimetableDataStore(context.timetableDataStore)
}
+
+ @Provides
+ @Singleton
+ fun provideBusDataStore(
+ @ApplicationContext context: Context
+ ): BusDataStore {
+ return BusDataStore(context.busDataStore)
+ }
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/di/error/ErrorHandlerModule.kt b/data/src/main/java/in/koreatech/koin/data/di/error/ErrorHandlerModule.kt
index 8b012cf3b..3774b45d8 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/error/ErrorHandlerModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/error/ErrorHandlerModule.kt
@@ -1,10 +1,8 @@
package `in`.koreatech.koin.data.di.error
-import `in`.koreatech.koin.data.error.BusErrorHandlerImpl
import `in`.koreatech.koin.data.error.DeptErrorHandlerImpl
import `in`.koreatech.koin.data.error.TokenErrorHandlerImpl
import `in`.koreatech.koin.data.error.UserErrorHandlerImpl
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
import `in`.koreatech.koin.domain.error.dept.DeptErrorHandler
import `in`.koreatech.koin.domain.error.token.TokenErrorHandler
import `in`.koreatech.koin.domain.error.user.UserErrorHandler
@@ -42,12 +40,6 @@ object ErrorHandlerModule {
@ApplicationContext applicationContext: Context
): TokenErrorHandler = TokenErrorHandlerImpl(applicationContext)
- @Provides
- @Singleton
- fun provideBusErrorHandler(
- @ApplicationContext applicationContext: Context
- ): BusErrorHandler = BusErrorHandlerImpl(applicationContext)
-
@Provides
@Singleton
fun provideOwnerErrorHandler(
diff --git a/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt b/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt
index 32207645a..99d9afdd4 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/network/NoAuthNetworkModule.kt
@@ -1,19 +1,28 @@
package `in`.koreatech.koin.data.di.network
-import `in`.koreatech.koin.core.qualifier.NoAuth
-import `in`.koreatech.koin.core.qualifier.ServerUrl
-import `in`.koreatech.koin.data.api.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import `in`.koreatech.koin.core.qualifier.NoAuth
+import `in`.koreatech.koin.core.qualifier.ServerUrl
import `in`.koreatech.koin.data.api.ArticleApi
-import javax.inject.Singleton
+import `in`.koreatech.koin.data.api.BusApi
+import `in`.koreatech.koin.data.api.CoopShopApi
+import `in`.koreatech.koin.data.api.DeptApi
+import `in`.koreatech.koin.data.api.DiningApi
+import `in`.koreatech.koin.data.api.LandApi
+import `in`.koreatech.koin.data.api.OwnerApi
+import `in`.koreatech.koin.data.api.StoreApi
+import `in`.koreatech.koin.data.api.TimetableApi
+import `in`.koreatech.koin.data.api.UserApi
+import `in`.koreatech.koin.data.api.VersionApi
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/BindsRepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/BindsRepositoryModule.kt
index caa592baa..543ed8c2a 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/repository/BindsRepositoryModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/repository/BindsRepositoryModule.kt
@@ -1,12 +1,13 @@
package `in`.koreatech.koin.data.di.repository
import dagger.Binds
-import dagger.BindsInstance
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import `in`.koreatech.koin.data.repository.BusRepositoryImpl
import `in`.koreatech.koin.data.repository.TimetableRepositoryImpl
import `in`.koreatech.koin.data.repository.firebase.messaging.FirebaseMessagingRepositoryImpl
+import `in`.koreatech.koin.domain.repository.BusRepository
import `in`.koreatech.koin.domain.repository.TimetableRepository
import `in`.koreatech.koin.domain.repository.firebase.messaging.FirebaseMessagingRepository
import javax.inject.Singleton
@@ -25,4 +26,10 @@ abstract class BindsRepositoryModule {
abstract fun bindsTimetableRepository(
timetableRepositoryImpl: TimetableRepositoryImpl
): TimetableRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsBusV2Repository(
+ busV2RepositoryImpl: BusRepositoryImpl
+ ): BusRepository
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt
index 6ce5082b4..089e32ff4 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt
@@ -1,14 +1,11 @@
package `in`.koreatech.koin.data.di.repository
-import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import `in`.koreatech.koin.core.qualifier.IoDispatcher
import `in`.koreatech.koin.data.repository.ArticleRepositoryImpl
-import `in`.koreatech.koin.data.repository.BusRepositoryImpl
import `in`.koreatech.koin.data.repository.CoopShopRepositoryImpl
import `in`.koreatech.koin.data.repository.DeptRepositoryImpl
import `in`.koreatech.koin.data.repository.DiningRepositoryImpl
@@ -27,7 +24,6 @@ import `in`.koreatech.koin.data.repository.UploadUrlRepositoryImpl
import `in`.koreatech.koin.data.repository.UserRepositoryImpl
import `in`.koreatech.koin.data.repository.VersionRepositoryImpl
import `in`.koreatech.koin.data.source.local.ArticleLocalDataSource
-import `in`.koreatech.koin.data.source.local.BusLocalDataSource
import `in`.koreatech.koin.data.source.local.DeptLocalDataSource
import `in`.koreatech.koin.data.source.local.SignupTermsLocalDataSource
import `in`.koreatech.koin.data.source.local.TokenLocalDataSource
@@ -35,7 +31,6 @@ import `in`.koreatech.koin.data.source.local.UploadImageLocalDataSource
import `in`.koreatech.koin.data.source.local.UserLocalDataSource
import `in`.koreatech.koin.data.source.local.VersionLocalDataSource
import `in`.koreatech.koin.data.source.remote.ArticleRemoteDataSource
-import `in`.koreatech.koin.data.source.remote.BusRemoteDataSource
import `in`.koreatech.koin.data.source.remote.CoopShopRemoteDataSource
import `in`.koreatech.koin.data.source.remote.DeptRemoteDataSource
import `in`.koreatech.koin.data.source.remote.DiningRemoteDataSource
@@ -48,7 +43,6 @@ import `in`.koreatech.koin.data.source.remote.UploadUrlRemoteDataSource
import `in`.koreatech.koin.data.source.remote.UserRemoteDataSource
import `in`.koreatech.koin.data.source.remote.VersionRemoteDataSource
import `in`.koreatech.koin.domain.repository.ArticleRepository
-import `in`.koreatech.koin.domain.repository.BusRepository
import `in`.koreatech.koin.domain.repository.CoopShopRepository
import `in`.koreatech.koin.domain.repository.DeptRepository
import `in`.koreatech.koin.domain.repository.DiningRepository
@@ -175,16 +169,6 @@ object RepositoryModule {
return DiningRepositoryImpl(diningRemoteDataSource)
}
- @Provides
- @Singleton
- fun provideBusRepository(
- @ApplicationContext applicationContext: Context,
- busLocalDataSource: BusLocalDataSource,
- busRemoteDataSource: BusRemoteDataSource
- ): BusRepository {
- return BusRepositoryImpl(applicationContext, busLocalDataSource, busRemoteDataSource)
- }
-
@Provides
@Singleton
fun provideStoreRepository(
diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt
index c533471b4..e34480e13 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/source/LocalDataSourceModule.kt
@@ -9,7 +9,6 @@ import dagger.hilt.components.SingletonComponent
import `in`.koreatech.koin.core.qualifier.IoDispatcher
import `in`.koreatech.koin.data.source.datastore.ArticleDataStore
import `in`.koreatech.koin.data.source.local.ArticleLocalDataSource
-import `in`.koreatech.koin.data.source.local.BusLocalDataSource
import `in`.koreatech.koin.data.source.local.DeptLocalDataSource
import `in`.koreatech.koin.data.source.local.SignupTermsLocalDataSource
import `in`.koreatech.koin.data.source.local.TokenLocalDataSource
@@ -47,14 +46,6 @@ object LocalDataSourceModule {
return VersionLocalDataSource(applicationContext)
}
- @Provides
- @Singleton
- fun provideBusLocalDataSource(
- @ApplicationContext applicationContext: Context
- ): BusLocalDataSource {
- return BusLocalDataSource(applicationContext)
- }
-
@Provides
@Singleton
fun provideDeptLocalDataSource(
diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt
index 5124d8017..65bee12b0 100644
--- a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt
+++ b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt
@@ -5,7 +5,6 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import `in`.koreatech.koin.data.api.ArticleApi
-import `in`.koreatech.koin.data.api.BusApi
import `in`.koreatech.koin.data.api.CoopShopApi
import `in`.koreatech.koin.data.api.DeptApi
import `in`.koreatech.koin.data.api.DiningApi
@@ -22,7 +21,6 @@ import `in`.koreatech.koin.data.api.auth.OwnerAuthApi
import `in`.koreatech.koin.data.api.auth.TimetableAuthApi
import `in`.koreatech.koin.data.api.auth.UserAuthApi
import `in`.koreatech.koin.data.source.remote.ArticleRemoteDataSource
-import `in`.koreatech.koin.data.source.remote.BusRemoteDataSource
import `in`.koreatech.koin.data.source.remote.CoopShopRemoteDataSource
import `in`.koreatech.koin.data.source.remote.DeptRemoteDataSource
import `in`.koreatech.koin.data.source.remote.DiningRemoteDataSource
@@ -98,14 +96,6 @@ object RemoteDataSourceModule {
return DiningRemoteDataSource(diningApi)
}
- @Provides
- @Singleton
- fun provideBusRemoteDataSource(
- busApi: BusApi,
- ): BusRemoteDataSource {
- return BusRemoteDataSource(busApi)
- }
-
@Provides
@Singleton
fun provideStoreRemoteDataSource(
diff --git a/data/src/main/java/in/koreatech/koin/data/error/BusErrorHandlerImpl.kt b/data/src/main/java/in/koreatech/koin/data/error/BusErrorHandlerImpl.kt
deleted file mode 100644
index 3072f4f5f..000000000
--- a/data/src/main/java/in/koreatech/koin/data/error/BusErrorHandlerImpl.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package `in`.koreatech.koin.data.error
-
-import `in`.koreatech.koin.data.util.handleCommonError
-import `in`.koreatech.koin.data.util.unknownErrorHandler
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import android.content.Context
-import dagger.hilt.android.qualifiers.ApplicationContext
-import retrofit2.HttpException
-import javax.inject.Inject
-
-class BusErrorHandlerImpl @Inject constructor(
- @ApplicationContext private val context: Context
-) : BusErrorHandler {
- override fun handleGetBusCoursesError(throwable: Throwable): ErrorHandler =
- throwable.handleCommonError(context) {
- unknownErrorHandler(context)
- }
-
- override fun handleGetBusTimetableError(throwable: Throwable): ErrorHandler =
- throwable.handleCommonError(context) {
- unknownErrorHandler(context)
- }
-
- override fun handleGetBusRemainTimeError(throwable: Throwable): ErrorHandler =
- throwable.handleCommonError(context) {
- unknownErrorHandler(context)
- }
-
- override fun handleGetBusCoursesErrorHandler(throwable: Throwable): ErrorHandler =
- throwable.handleCommonError(context) {
- unknownErrorHandler(context)
- }
-
- override fun handleSearchBusError(throwable: Throwable): ErrorHandler =
- throwable.handleCommonError(context) {
- unknownErrorHandler(context)
- }
-}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/BusCourseMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/BusCourseMapper.kt
deleted file mode 100644
index d4f785478..000000000
--- a/data/src/main/java/in/koreatech/koin/data/mapper/BusCourseMapper.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package `in`.koreatech.koin.data.mapper
-
-import `in`.koreatech.koin.data.R
-import `in`.koreatech.koin.domain.model.bus.BusDirection
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import android.content.Context
-
-fun BusCourse.toCourseNameString(context: Context): String {
- return when (busType) {
- BusType.Shuttle -> {
- "$region ${
- if (direction == BusDirection.ToKoreatech) context.getString(
- R.string.bus_course_shuttle_to_koreatech
- ) else context.getString(R.string.bus_course_shuttle_from_koreatech)
- }"
- }
- BusType.Commuting -> {
- "$region ${
- if (direction == BusDirection.ToKoreatech) context.getString(R.string.bus_course_commuting_to_koreatech) else context.getString(
- R.string.bus_course_commuting_from_koreatech
- )
- }"
- }
- BusType.Express -> {
- if (direction == BusDirection.ToKoreatech) context.getString(R.string.bus_course_express_to_koreatech) else context.getString(
- R.string.bus_course_express_from_koreatech
- )
- }
- else -> ""
- }
-}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/BusMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/BusMapper.kt
deleted file mode 100644
index 917423a5a..000000000
--- a/data/src/main/java/in/koreatech/koin/data/mapper/BusMapper.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-package `in`.koreatech.koin.data.mapper
-
-import android.content.Context
-import `in`.koreatech.koin.data.R
-import `in`.koreatech.koin.data.response.bus.BusCourseResponse
-import `in`.koreatech.koin.data.response.bus.BusResponse
-import `in`.koreatech.koin.data.response.bus.BusSearchResponse
-import `in`.koreatech.koin.data.response.bus.CityBusDepartTimesResponse
-import `in`.koreatech.koin.data.response.bus.CityBusInfoResponse
-import `in`.koreatech.koin.data.response.bus.CityBusTimetableResponse
-import `in`.koreatech.koin.data.response.bus.ExpressBusRouteResponse
-import `in`.koreatech.koin.data.response.bus.ExpressBusTimetableResponse
-import `in`.koreatech.koin.data.response.bus.ShuttleBusRouteResponse
-import `in`.koreatech.koin.data.response.bus.ShuttleBusTimetableResponse
-import `in`.koreatech.koin.data.util.nowTime
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.city.numberToCityBusNumber
-import `in`.koreatech.koin.domain.model.bus.city.toCityBusDayType
-import `in`.koreatech.koin.domain.model.bus.city.toCityBusGeneralDestination
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusRoute
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import `in`.koreatech.koin.domain.model.bus.toBusDirection
-import `in`.koreatech.koin.domain.model.bus.toBusType
-import java.time.LocalDate
-
-fun BusCourseResponse.toBusCourse() = BusCourse(
- busType = busType.toBusType(),
- direction = direction.toBusDirection(),
- region = region
-)
-
-fun ShuttleBusTimetableResponse.toShuttleBusTimetable(): BusTimetable.ShuttleBusTimetable {
- return BusTimetable.ShuttleBusTimetable(
- routes = this.routes.map {
- it.toShuttleBusRoute()
- },
- updatedAt = updatedAt
- )
-}
-
-fun ExpressBusTimetableResponse.toExpressBusTimetable(): BusTimetable.ExpressBusTimetable {
- return BusTimetable.ExpressBusTimetable(
- routes = this.routes.toExpressBusRoute(),
- updatedAt = updatedAt
- )
-}
-
-fun CityBusTimetableResponse.toCityBusTimetable(): BusTimetable.CityBusTimetable {
- return BusTimetable.CityBusTimetable(
- busInfos = busInfo.toCityBusNodeInfo(),
- departTimes = departTimes.getTodayDepartTimes(),
- updatedAt = updatedAt
- )
-}
-
-fun ShuttleBusRouteResponse.toShuttleBusRoute(): BusRoute.ShuttleBusRoute {
- return BusRoute.ShuttleBusRoute(
- routeName = routeName,
- arrivalInfo = arrivalInfo.map { route ->
- BusNodeInfo.ShuttleNodeInfo(
- node = route.nodeName,
- arrivalTime = route.arrivalTime
- )
- }
- )
-}
-
-fun List.toExpressBusRoute(): BusRoute.ExpressBusRoute {
- return BusRoute.ExpressBusRoute(
- arrivalInfo = map { route ->
- BusNodeInfo.ExpressNodeInfo(
- departureTime = route.departure,
- arrivalTime = route.arrival,
- charge = route.charge
- )
- }
- )
-}
-
-fun CityBusInfoResponse.toCityBusNodeInfo(): BusNodeInfo.CityBusNodeInfo {
- return BusNodeInfo.CityBusNodeInfo(
- busNumber = number.numberToCityBusNumber,
- departNode = departNode.toCityBusGeneralDestination,
- arrivalNode = arrivalNode.toCityBusGeneralDestination
- )
-}
-
-fun List.getTodayDepartTimes(): List {
- this.map { times ->
- val dayType = times.dayOfWeek.toCityBusDayType
- // 평일 or 주말
- if (dayType.daysOfWeek.contains(LocalDate.now().dayOfWeek)) {
- return times.departInfo
- }
- }
- return this.first().departInfo // 기본으로 평일 리스트 반환
-}
-
-fun BusResponse.toShuttleBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode
-): BusArrivalInfo.ShuttleBusArrivalInfo {
- val time = nowTime
- val nowBusRemainTimeSecond = nowBus?.remainTimeSecond
- val nextBusRemainTimeSecond = nextBus?.remainTimeSecond
-
- return BusArrivalInfo.ShuttleBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = nowBusRemainTimeSecond,
- nextBusRemainTime = nextBus?.remainTimeSecond,
- nowBusArrivalTime = nowBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- nextBusArrivalTime = nextBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- criteria = time
- )
-}
-
-
-fun BusResponse.toCommutingBusRemainTimePair(
- departure: BusNode,
- arrival: BusNode
-): BusArrivalInfo.CommutingBusArrivalInfo {
- val time = nowTime
- val nowBusRemainTimeSecond = nowBus?.remainTimeSecond
- val nextBusRemainTimeSecond = nextBus?.remainTimeSecond
-
- return BusArrivalInfo.CommutingBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = nowBusRemainTimeSecond,
- nextBusRemainTime = nextBus?.remainTimeSecond,
- nowBusArrivalTime = nowBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- nextBusArrivalTime = nextBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- criteria = time
- )
-}
-
-fun BusResponse.toExpressBusRemainTimePair(
- departure: BusNode,
- arrival: BusNode
-): BusArrivalInfo.ExpressBusArrivalInfo {
- val time = nowTime
- val nowBusRemainTimeSecond = nowBus?.remainTimeSecond
- val nextBusRemainTimeSecond = nextBus?.remainTimeSecond
-
- return BusArrivalInfo.ExpressBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = nowBusRemainTimeSecond,
- nextBusRemainTime = nextBus?.remainTimeSecond,
- nowBusArrivalTime = nowBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- nextBusArrivalTime = nextBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- criteria = time
- )
-}
-
-fun BusResponse.toCityBusRemainTimePair(
- departure: BusNode,
- arrival: BusNode
-): BusArrivalInfo.CityBusArrivalInfo {
- val time = nowTime
- val nowBusRemainTimeSecond = nowBus?.remainTimeSecond
- val nextBusRemainTimeSecond = nextBus?.remainTimeSecond
-
- return BusArrivalInfo.CityBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = nowBusRemainTimeSecond,
- nextBusRemainTime = nextBus?.remainTimeSecond,
- nowBusArrivalTime = nowBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- nextBusArrivalTime = nextBusRemainTimeSecond?.let { time.plusSeconds(it).plusMinutes(1) },
- busNumber = nowBus?.busNumber ?: 0,
- criteria = time
- )
-}
-
-fun BusSearchResponse.toBusSearchResult(context: Context): BusSearchResult {
- return BusSearchResult(
- busType = busName.toBusType(),
- busTimeString = busTime ?: context.getString(R.string.bus_end_information)
- )
-}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/repository/BusRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/BusRepositoryImpl.kt
index 17636bad9..0139d6ea6 100644
--- a/data/src/main/java/in/koreatech/koin/data/repository/BusRepositoryImpl.kt
+++ b/data/src/main/java/in/koreatech/koin/data/repository/BusRepositoryImpl.kt
@@ -1,123 +1,81 @@
package `in`.koreatech.koin.data.repository
-import `in`.koreatech.koin.data.constant.BUS_REQUEST_DATE_FORMAT
-import `in`.koreatech.koin.data.constant.BUS_REQUEST_TIME_FORMAT
-import `in`.koreatech.koin.data.mapper.*
import `in`.koreatech.koin.data.source.local.BusLocalDataSource
import `in`.koreatech.koin.data.source.remote.BusRemoteDataSource
-import `in`.koreatech.koin.domain.model.bus.*
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusRoute
+import `in`.koreatech.koin.domain.model.bus.BusNotice
+import `in`.koreatech.koin.domain.model.bus.BusSearchResult
+import `in`.koreatech.koin.domain.model.bus.CityTimetable
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetable
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourses
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetable
import `in`.koreatech.koin.domain.repository.BusRepository
-import android.content.Context
-import dagger.hilt.android.qualifiers.ApplicationContext
-import `in`.koreatech.koin.domain.model.bus.city.CityBusInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import java.time.LocalDateTime
+import java.time.LocalDate
+import java.time.LocalTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject
class BusRepositoryImpl @Inject constructor(
- @ApplicationContext private val context: Context,
- private val busLocalDataSource: BusLocalDataSource,
- private val busRemoteDataSource: BusRemoteDataSource
+ private val busRemoteDataSource: BusRemoteDataSource,
+ private val busLocalDataSource: BusLocalDataSource
) : BusRepository {
- override suspend fun getShuttleBusCourses(): List> {
- return busRemoteDataSource.getBusCourses()
- .map { it.toBusCourse().let { it to it.toCourseNameString(context) } }
- }
-
- override suspend fun getExpressBusCourses(): List> {
- return busLocalDataSource.getExpressCourses()
- .map { it to it.toCourseNameString(context) }
- }
-
- override suspend fun getShuttleBusTimetable(busCourse: BusCourse): BusTimetable.ShuttleBusTimetable {
- return if (busCourse.busType == BusType.Shuttle) {
- busRemoteDataSource.getShuttleBusTimetable(
- busDirection = busCourse.direction.busDirectionString,
- region = busCourse.region
- )
- } else {
- busRemoteDataSource.getCommutingBusTimetable(
- busDirection = busCourse.direction.busDirectionString,
- region = busCourse.region
- )
- }.toShuttleBusTimetable()
+ override suspend fun fetchBusNotice(): Result {
+ return runCatching {
+ busRemoteDataSource.fetchBusNotice().toBusNotice()
+ }
}
- override suspend fun getExpressBusTimetable(busCourse: BusCourse): BusTimetable.ExpressBusTimetable {
- return busRemoteDataSource.getExpressBusTimetable(
- busDirection = busCourse.direction.busDirectionString
- ).toExpressBusTimetable()
+ override suspend fun fetchShuttleTimetable(id: String): Result {
+ return runCatching {
+ busRemoteDataSource.fetchShuttleTimetable(id).toShuttleTimetable()
+ }
}
- override suspend fun getCityBusTimetable(cityBusInfo: CityBusInfo): BusTimetable.CityBusTimetable {
- return busRemoteDataSource.getCityBusTimetable(
- number = cityBusInfo.busNumber,
- direction = cityBusInfo.direction
- ).toCityBusTimetable()
+ override suspend fun fetchShuttleCourses(): Result {
+ return runCatching {
+ busRemoteDataSource.fetchShuttleCourses().toShuttleCourses()
+ }
}
- override suspend fun getShuttleBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.ShuttleBusArrivalInfo {
- return busRemoteDataSource.getBuses(
- busType = BusType.Shuttle.busTypeString,
- departure = departure.busNodeString,
- arrival = arrival.busNodeString
- ).toShuttleBusArrivalInfo(departure, arrival)
+ override suspend fun fetchExpressTimetable(direction: String): Result {
+ return runCatching {
+ busRemoteDataSource.fetchExpressTimetable(direction).toExpressTimetable()
+ }
}
- override suspend fun getExpressBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.ExpressBusArrivalInfo {
- return busRemoteDataSource.getBuses(
- busType = BusType.Express.busTypeString,
- departure = departure.busNodeString,
- arrival = arrival.busNodeString
- ).toExpressBusRemainTimePair(departure, arrival)
+ override suspend fun fetchCityTimetable(number: Int, direction: String): Result {
+ return runCatching {
+ busRemoteDataSource.fetchCityTimetable(number, direction).toCityTimetable()
+ }
}
- override suspend fun getCommutingBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.CommutingBusArrivalInfo {
- return busRemoteDataSource.getBuses(
- busType = BusType.Commuting.busTypeString,
- departure = departure.busNodeString,
- arrival = arrival.busNodeString
- ).toCommutingBusRemainTimePair(departure, arrival)
+ override suspend fun fetchBusSearchResult(
+ date: LocalDate,
+ time: LocalTime,
+ busType: String,
+ departure: String,
+ arrival: String
+ ): Result> {
+ return runCatching {
+ busRemoteDataSource.fetchBusSearchResult(
+ date = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(date),
+ time = DateTimeFormatter.ofPattern("HH:mm").format(time),
+ busType = busType,
+ departure = departure,
+ arrival = arrival
+ ).schedules?.map { it.toBusSearchResult() }.orEmpty()
+ }
}
- override suspend fun getCityBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.CityBusArrivalInfo {
- return busRemoteDataSource.getBuses(
- busType = BusType.City.busTypeString,
- departure = departure.busNodeString,
- arrival = arrival.busNodeString
- ).toCityBusRemainTimePair(departure, arrival)
+ override suspend fun getLastShownNoticeId(): Result {
+ return runCatching {
+ busLocalDataSource.getLastShownNoticeId()
+ }
}
- override suspend fun searchBus(
- time: LocalDateTime,
- departure: BusNode,
- arrival: BusNode
- ): List {
- return busRemoteDataSource.searchBus(
- date = time.format(DateTimeFormatter.ofPattern(BUS_REQUEST_DATE_FORMAT)),
- time = time.format(DateTimeFormatter.ofPattern(BUS_REQUEST_TIME_FORMAT)),
- departure = departure.busNodeString,
- arrival = arrival.busNodeString
- ).map {
- it.toBusSearchResult(context)
+ override suspend fun saveLastShownNoticeId(id: Int): Result {
+ return runCatching {
+ busLocalDataSource.saveLastShownNoticeId(id)
}
}
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt
index 6a696135c..2b6ac49e6 100644
--- a/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt
+++ b/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt
@@ -2,9 +2,9 @@ package `in`.koreatech.koin.data.repository
import `in`.koreatech.koin.data.mapper.toUser
import `in`.koreatech.koin.data.mapper.toUserRequest
+import `in`.koreatech.koin.data.mapper.toUserRequestWithPassword
import `in`.koreatech.koin.data.request.owner.OwnerLoginRequest
import `in`.koreatech.koin.data.request.user.ABTestRequest
-import `in`.koreatech.koin.data.mapper.toUserRequestWithPassword
import `in`.koreatech.koin.data.request.user.IdRequest
import `in`.koreatech.koin.data.request.user.LoginRequest
import `in`.koreatech.koin.data.request.user.PasswordRequest
@@ -43,11 +43,11 @@ class UserRepositoryImpl @Inject constructor(
}
override fun ownerTokenIsValid(): Boolean {
- return runBlocking{
+ return runBlocking {
try {
userRemoteDataSource.ownerTokenIsValid()
true
- } catch (e: HttpException){
+ } catch (e: HttpException) {
if (e.code() == 401) false
else throw e
}
@@ -55,6 +55,12 @@ class UserRepositoryImpl @Inject constructor(
}
}
+ override suspend fun fetchUserInfo() {
+ userRemoteDataSource.getUserInfo().toUser().also {
+ userLocalDataSource.updateUserInfo(it)
+ }
+ }
+
override suspend fun getUserInfo(): User {
return userRemoteDataSource.getUserInfo().toUser().also {
userLocalDataSource.updateUserInfo(it)
@@ -131,6 +137,7 @@ class UserRepositoryImpl @Inject constructor(
return ABTest(it.variableName, it.accessHistoryId)
}
}
+
override suspend fun updateUserPassword(user: User, hashedPassword: String) {
when (user) {
User.Anonymous -> throw IllegalAccessException("Updating anonymous user is not supported")
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusCourseResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusCourseResponse.kt
deleted file mode 100644
index 48f6bf634..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/BusCourseResponse.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import `in`.koreatech.koin.domain.model.bus.BusDirection
-import com.google.gson.annotations.SerializedName
-
-data class BusCourseResponse(
- @SerializedName("bus_type") val busType: String,
- @SerializedName("direction") val direction: String,
- @SerializedName("region") val region: String
-)
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusNoticeResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusNoticeResponse.kt
new file mode 100644
index 000000000..25095d091
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/BusNoticeResponse.kt
@@ -0,0 +1,12 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.BusNotice
+
+data class BusNoticeResponse(
+ @SerializedName("id") val id: Int,
+ @SerializedName("title") val title: String
+) {
+
+ fun toBusNotice() = BusNotice(id, title)
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusRemainTimeResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusRemainTimeResponse.kt
deleted file mode 100644
index 69af08b1b..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/BusRemainTimeResponse.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class BusRemainTimeResponse(
- @SerializedName("bus_number") val busNumber: Int? = null,
- @SerializedName("remain_time") val remainTimeSecond: Long
-)
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusResponse.kt
deleted file mode 100644
index 9e3f8cdfd..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/BusResponse.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class BusResponse(
- @SerializedName("bus_type") val busType: String,
- @SerializedName("now_bus") val nowBus: BusRemainTimeResponse? = null,
- @SerializedName("next_bus") val nextBus: BusRemainTimeResponse? = null
-)
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusRouteResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusRouteResponse.kt
deleted file mode 100644
index 6e67ec1b9..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/BusRouteResponse.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class ShuttleBusRouteResponse(
- @SerializedName("route_name") val routeName: String,
- @SerializedName("arrival_info") val arrivalInfo: List
-)
-
-data class ShuttleBusNodeInfoResponse(
- @SerializedName("node_name") val nodeName: String,
- @SerializedName("arrival_time") val arrivalTime: String
-)
-
-data class ExpressBusRouteResponse(
- @SerializedName("departure") val departure: String,
- @SerializedName("arrival") val arrival: String,
- @SerializedName("charge") val charge: Int,
-)
-
-data class CityBusRouteResponse(
- val startBusNode: String,
- val timeInfo: String
-)
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResponse.kt
deleted file mode 100644
index 7d0d6da6c..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResponse.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class BusSearchResponse(
- @SerializedName("bus_name") val busName: String,
- @SerializedName("bus_time") val busTime: String?
-)
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResultResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResultResponse.kt
new file mode 100644
index 000000000..3d99df870
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/BusSearchResultResponse.kt
@@ -0,0 +1,25 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.BusSearchResult
+import java.time.LocalTime
+
+data class BusSearchResultWrapperResponse(
+ @SerializedName("depart") val departure: String?,
+ @SerializedName("arrival") val arrival: String?,
+ @SerializedName("depart_date") val departDate: String?,
+ @SerializedName("depart_time") val departTime: String?,
+ @SerializedName("schedule") val schedules: List?,
+)
+
+data class BusSearchResultResponse(
+ @SerializedName("bus_type") val busType: String?,
+ @SerializedName("bus_name") val busName: String?,
+ @SerializedName("depart_time") val departureTime: String?,
+) {
+ fun toBusSearchResult() = BusSearchResult(
+ busType = busType.orEmpty(),
+ busName = busName.orEmpty(),
+ departureTime = LocalTime.parse(departureTime)
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusInfoResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusInfoResponse.kt
new file mode 100644
index 000000000..72c36a3cf
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusInfoResponse.kt
@@ -0,0 +1,17 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.CityBusInfo
+
+data class CityBusInfoResponse(
+ @SerializedName("number") val number: Int,
+ @SerializedName("depart_node") val departNode: String,
+ @SerializedName("arrival_node") val arriveNode: String,
+) {
+
+ fun toCityBusInfo() = CityBusInfo(
+ number = number,
+ departNode = departNode,
+ arriveNode = arriveNode,
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusTimetableResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusTimetableResponse.kt
deleted file mode 100644
index 4a6b609a3..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/CityBusTimetableResponse.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class CityBusTimetableResponse(
- @SerializedName("updated_at") val updatedAt: String,
- @SerializedName("bus_info") val busInfo: CityBusInfoResponse,
- @SerializedName("bus_timetables") val departTimes: List
-)
-
-data class CityBusInfoResponse(
- @SerializedName("number") val number: Int,
- @SerializedName("depart_node") val departNode: String,
- @SerializedName("arrival_node") val arrivalNode: String
-)
-
-data class CityBusDepartTimesResponse(
- @SerializedName("day_of_week") val dayOfWeek: String,
- @SerializedName("depart_info") val departInfo: List
-)
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableItemResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableItemResponse.kt
new file mode 100644
index 000000000..73b973e1a
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableItemResponse.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.CityTimetableItem
+
+data class CityTimetableItemResponse(
+ @SerializedName("day_of_week") val dayOfWeek: String?,
+ @SerializedName("depart_info") val departureTimes: List?
+) {
+ fun toCityTimetableItem() = CityTimetableItem(
+ dayOfWeek = dayOfWeek.orEmpty(),
+ departureTimes = departureTimes.orEmpty()
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableResponse.kt
new file mode 100644
index 000000000..bbb9dcd5f
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/CityTimetableResponse.kt
@@ -0,0 +1,19 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.CityBusInfo
+import `in`.koreatech.koin.domain.model.bus.CityTimetable
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+data class CityTimetableResponse(
+ @SerializedName("bus_timetables") val timetable: List?,
+ @SerializedName("bus_info") val busInfo: CityBusInfoResponse?,
+ @SerializedName("updated_at") val updatedAt: String?,
+) {
+ fun toCityTimetable() = CityTimetable(
+ timetable = timetable?.map { it.toCityTimetableItem() }.orEmpty(),
+ busInfo = busInfo?.toCityBusInfo() ?: CityBusInfo(0, "", ""),
+ updatedAt = LocalDateTime.parse(updatedAt ?: "1999-04-29 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableItemResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableItemResponse.kt
new file mode 100644
index 000000000..adb302b55
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableItemResponse.kt
@@ -0,0 +1,16 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetableItem
+
+data class ExpressTimetableItemResponse(
+ @SerializedName("arrival") val arrivalTime: String?,
+ @SerializedName("departure") val departureTime: String?,
+ @SerializedName("charge") val charge: Int?,
+) {
+ fun toExpressTimetableItem() = ExpressTimetableItem(
+ arrivalTime = arrivalTime ?: "",
+ departureTime = departureTime ?: "",
+ charge = charge ?: 0,
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableResponse.kt
new file mode 100644
index 000000000..35c57aa86
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ExpressTimetableResponse.kt
@@ -0,0 +1,16 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetable
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+data class ExpressTimetableResponse(
+ @SerializedName("bus_timetables") val timetable: List?,
+ @SerializedName("updated_at") val updatedAt: String?,
+) {
+ fun toExpressTimetable() = ExpressTimetable(
+ timetable = timetable?.map { it.toExpressTimetableItem() }.orEmpty(),
+ updatedAt = LocalDateTime.parse(updatedAt ?: "1999-04-29 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleBusRouteResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleBusRouteResponse.kt
deleted file mode 100644
index 0adababca..000000000
--- a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleBusRouteResponse.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package `in`.koreatech.koin.data.response.bus
-
-import com.google.gson.annotations.SerializedName
-
-data class ShuttleBusTimetableResponse(
- @SerializedName("bus_timetables") val routes: List,
- @SerializedName("updated_at") val updatedAt: String
-)
-
-data class ExpressBusTimetableResponse(
- @SerializedName("bus_timetables") val routes: List,
- @SerializedName("updated_at") val updatedAt: String
-)
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseResponse.kt
new file mode 100644
index 000000000..6d585b611
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseResponse.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourse
+
+data class ShuttleCourseResponse(
+ @SerializedName("region") val region: String?,
+ @SerializedName("routes") val routes: List?
+) {
+ fun toShuttleCourse() = ShuttleCourse(
+ region = region.orEmpty(),
+ routes = routes?.map { it.toShuttleCourseRoute() }.orEmpty()
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseRouteResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseRouteResponse.kt
new file mode 100644
index 000000000..9d925bd9b
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCourseRouteResponse.kt
@@ -0,0 +1,18 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourseRoute
+
+data class ShuttleCourseRouteResponse(
+ @SerializedName("id") val id: String?,
+ @SerializedName("type") val type: String?,
+ @SerializedName("route_name") val routeName: String?,
+ @SerializedName("sub_name") val subName: String?,
+) {
+ fun toShuttleCourseRoute() = ShuttleCourseRoute(
+ id = id.orEmpty(),
+ type = type.orEmpty(),
+ routeName = routeName.orEmpty(),
+ subName = subName.orEmpty()
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCoursesResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCoursesResponse.kt
new file mode 100644
index 000000000..932b42560
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleCoursesResponse.kt
@@ -0,0 +1,16 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourses
+import `in`.koreatech.koin.domain.model.bus.ShuttleSemester
+import java.time.LocalDate
+
+data class ShuttleCoursesResponse(
+ @SerializedName("route_regions") val courses: List?,
+ @SerializedName("semester_info") val semester: ShuttleSemesterResponse?,
+) {
+ fun toShuttleCourses() = ShuttleCourses(
+ courses = courses?.map { it.toShuttleCourse() }.orEmpty(),
+ semester = semester?.toShuttleSemester() ?: ShuttleSemester("", LocalDate.MIN, LocalDate.MIN)
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleSemesterResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleSemesterResponse.kt
new file mode 100644
index 000000000..8d1a7aabf
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleSemesterResponse.kt
@@ -0,0 +1,17 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleSemester
+import java.time.LocalDate
+
+data class ShuttleSemesterResponse(
+ @SerializedName("name") val name: String?,
+ @SerializedName("from") val from: String?,
+ @SerializedName("to") val to: String?,
+) {
+ fun toShuttleSemester() = ShuttleSemester(
+ name = name.orEmpty(),
+ from = LocalDate.parse(from.orEmpty()),
+ to = LocalDate.parse(to.orEmpty())
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableNodeInfoResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableNodeInfoResponse.kt
new file mode 100644
index 000000000..80508dc6b
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableNodeInfoResponse.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetableNodeInfo
+
+data class ShuttleTimetableNodeInfoResponse(
+ @SerializedName("name") val name: String?,
+ @SerializedName("detail") val detail: String?,
+) {
+ fun toShuttleTimetableNodeInfo() = ShuttleTimetableNodeInfo(
+ name = name.orEmpty(),
+ detail = detail.orEmpty()
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableResponse.kt
new file mode 100644
index 000000000..eb548f7cc
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableResponse.kt
@@ -0,0 +1,22 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetable
+
+data class ShuttleTimetableResponse(
+ @SerializedName("region") val region: String?,
+ @SerializedName("route_type") val routeType: String?,
+ @SerializedName("route_name") val routeName: String?,
+ @SerializedName("sub_name") val subTitle: String?,
+ @SerializedName("node_info") val nodeInfo: List?,
+ @SerializedName("route_info") val routeInfo: List?,
+) {
+ fun toShuttleTimetable() = ShuttleTimetable(
+ region = region.orEmpty(),
+ routeType = routeType.orEmpty(),
+ routeName = routeName.orEmpty(),
+ subTitle = subTitle.orEmpty(),
+ nodeInfo = nodeInfo?.map { it.toShuttleTimetableNodeInfo() }.orEmpty(),
+ routeInfo = routeInfo?.map { it.toShuttleTimetableRouteInfo() }.orEmpty()
+ )
+}
diff --git a/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableRouteInfoResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableRouteInfoResponse.kt
new file mode 100644
index 000000000..c84202cc3
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/response/bus/ShuttleTimetableRouteInfoResponse.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.koin.data.response.bus
+
+import com.google.gson.annotations.SerializedName
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetableRouteInfo
+
+data class ShuttleTimetableRouteInfoResponse(
+ @SerializedName("name") val name: String?,
+ @SerializedName("arrival_time") val arrivalTimes: List?,
+) {
+ fun toShuttleTimetableRouteInfo() = ShuttleTimetableRouteInfo(
+ name = name.orEmpty(),
+ arrivalTimes = arrivalTimes?.map { it.orEmpty() }.orEmpty()
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/source/datastore/BusDataStore.kt b/data/src/main/java/in/koreatech/koin/data/source/datastore/BusDataStore.kt
new file mode 100644
index 000000000..c33108bf6
--- /dev/null
+++ b/data/src/main/java/in/koreatech/koin/data/source/datastore/BusDataStore.kt
@@ -0,0 +1,34 @@
+package `in`.koreatech.koin.data.source.datastore
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.core.intPreferencesKey
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class BusDataStore @Inject constructor(
+ private val dataStore: DataStore
+) {
+
+ suspend fun getLastShownNoticeId(): Int {
+ return dataStore.data.catch {
+ emit(emptyPreferences())
+ }.map { preferences ->
+ preferences[KEY_LAST_SHOWN_NOTICE_ID]
+ }.first()!!
+ }
+
+ suspend fun saveLastShownNoticeId(id: Int) {
+ dataStore.edit { preferences ->
+ preferences[KEY_LAST_SHOWN_NOTICE_ID] = id
+ }
+ }
+
+ companion object {
+ private val KEY_LAST_SHOWN_NOTICE_ID = intPreferencesKey("last_shown_notice_id")
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/BusLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/BusLocalDataSource.kt
index ef397abcf..9ec5d5be9 100644
--- a/data/src/main/java/in/koreatech/koin/data/source/local/BusLocalDataSource.kt
+++ b/data/src/main/java/in/koreatech/koin/data/source/local/BusLocalDataSource.kt
@@ -1,37 +1,17 @@
package `in`.koreatech.koin.data.source.local
-import `in`.koreatech.koin.data.R
-import `in`.koreatech.koin.data.response.bus.CityBusRouteResponse
-import `in`.koreatech.koin.domain.model.bus.BusDirection
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import android.content.Context
-import dagger.hilt.android.qualifiers.ApplicationContext
+import `in`.koreatech.koin.data.source.datastore.BusDataStore
import javax.inject.Inject
class BusLocalDataSource @Inject constructor(
- @ApplicationContext private val context: Context
+ private val busDataStore: BusDataStore
) {
- fun getCityBusTimetable(): List {
- return listOf(
- CityBusRouteResponse(context.getString(R.string.bus_citybus_timetable_terminal_node), context.getString(R.string.bus_citybus_timetable_terminal_time)),
- CityBusRouteResponse(context.getString(R.string.bus_citybus_timetable_byeongcheon_node), context.getString(R.string.bus_citybus_timetable_byeongcheon_time)),
- CityBusRouteResponse(context.getString(R.string.bus_citybus_timetable_require_time), context.getString(R.string.bus_citybus_timetable_require_time_value))
- )
+
+ suspend fun getLastShownNoticeId(): Int {
+ return busDataStore.getLastShownNoticeId()
}
- fun getExpressCourses(): List {
- return listOf(
- BusCourse(
- busType = BusType.Express,
- direction = BusDirection.ToKoreatech,
- region = ""
- ),
- BusCourse(
- busType = BusType.Express,
- direction = BusDirection.FromKoreatech,
- region = ""
- )
- )
+ suspend fun saveLastShownNoticeId(id: Int) {
+ busDataStore.saveLastShownNoticeId(id)
}
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/BusRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/BusRemoteDataSource.kt
index 9139c6d44..341bb61b0 100644
--- a/data/src/main/java/in/koreatech/koin/data/source/remote/BusRemoteDataSource.kt
+++ b/data/src/main/java/in/koreatech/koin/data/source/remote/BusRemoteDataSource.kt
@@ -1,57 +1,51 @@
package `in`.koreatech.koin.data.source.remote
import `in`.koreatech.koin.data.api.BusApi
-import `in`.koreatech.koin.data.response.bus.*
+import `in`.koreatech.koin.data.response.bus.BusNoticeResponse
+import `in`.koreatech.koin.data.response.bus.BusSearchResultWrapperResponse
+import `in`.koreatech.koin.data.response.bus.CityTimetableResponse
+import `in`.koreatech.koin.data.response.bus.ExpressTimetableResponse
+import `in`.koreatech.koin.data.response.bus.ShuttleCoursesResponse
+import `in`.koreatech.koin.data.response.bus.ShuttleTimetableResponse
import javax.inject.Inject
class BusRemoteDataSource @Inject constructor(
private val busApi: BusApi
) {
- suspend fun getBusCourses(): List {
- return busApi.getBusCourses()
- }
- suspend fun getShuttleBusTimetable(
- busDirection: String,
- region: String
- ): ShuttleBusTimetableResponse {
- return busApi.getShuttleBusTimetable("shuttle", busDirection, region)
+ suspend fun fetchBusNotice(): BusNoticeResponse {
+ return busApi.fetchBusNotice()
}
- suspend fun getCommutingBusTimetable(
- busDirection: String,
- region: String
- ): ShuttleBusTimetableResponse {
- return busApi.getShuttleBusTimetable("commuting", busDirection, region)
+ suspend fun fetchShuttleTimetable(id: String): ShuttleTimetableResponse {
+ return busApi.fetchShuttleTimetable(id)
}
- suspend fun getExpressBusTimetable(
- busDirection: String
- ): ExpressBusTimetableResponse {
- return busApi.getExpressBusTimetable(busDirection, "")
+ suspend fun fetchShuttleCourses(): ShuttleCoursesResponse {
+ return busApi.fetchShuttleCourses()
}
- suspend fun getCityBusTimetable(
- number: Int,
- direction: String
- ): CityBusTimetableResponse {
- return busApi.getCityBusTimetable(number, direction)
+ suspend fun fetchExpressTimetable(direction: String): ExpressTimetableResponse {
+ return busApi.fetchExpressTimetable(direction)
}
- suspend fun searchBus(
- date: String, // yyyy-MM-dd
- time: String, // HH:mm
- departure: String,
- arrival: String
- ) : List {
- return busApi.searchBus(date, time, departure, arrival)
+ suspend fun fetchCityTimetable(busNumber: Int, direction: String): CityTimetableResponse {
+ return busApi.fetchCityTimetable(busNumber, direction)
}
- suspend fun getBuses(
+ suspend fun fetchBusSearchResult(
+ date: String,
+ time: String,
busType: String,
departure: String,
arrival: String
- ): BusResponse {
- return busApi.getBus(busType, departure, arrival)
+ ): BusSearchResultWrapperResponse {
+ return busApi.fetchBusSearchResult(
+ date = date,
+ time = time,
+ busType = busType,
+ departure = departure,
+ arrival = arrival
+ )
}
}
\ No newline at end of file
diff --git a/data/src/main/java/in/koreatech/koin/data/util/BusUtil.kt b/data/src/main/java/in/koreatech/koin/data/util/BusUtil.kt
deleted file mode 100644
index d5e4d80f8..000000000
--- a/data/src/main/java/in/koreatech/koin/data/util/BusUtil.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package `in`.koreatech.koin.data.util
-
-import `in`.koreatech.koin.data.R
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import android.content.Context
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
-
-fun BusNode.localized(context: Context) = when (this) {
- BusNode.Koreatech -> context.getString(R.string.bus_node_koreatech)
- BusNode.Station -> context.getString(R.string.bus_node_station)
- BusNode.Terminal -> context.getString(R.string.bus_node_terminal)
-}
-
-fun Long?.toBusRemainTimeFormatted(context: Context) = this?.let {
- if(it >= 0) {
- LocalTime.ofSecondOfDay(it)
- .format(DateTimeFormatter.ofPattern(context.getString(R.string.bus_remain_time_format)))
- } else {
- null
- }
-} ?: context.getString(R.string.bus_no_remain_time)
-
-fun LocalTime.toBusArrivalTimeFormatted(context: Context) = format(
- DateTimeFormatter.ofPattern(context.getString(R.string.bus_arrival_time_format))
-)
-
-fun BusArrivalInfo.localized(context: Context) = when(this) {
- is BusArrivalInfo.CityBusArrivalInfo -> context.getString(R.string.bus_name_city)
- is BusArrivalInfo.CommutingBusArrivalInfo -> context.getString(R.string.bus_name_commuting)
- is BusArrivalInfo.ExpressBusArrivalInfo -> context.getString(R.string.bus_name_express)
- is BusArrivalInfo.ShuttleBusArrivalInfo -> context.getString(R.string.bus_name_shuttle)
-}
-
-fun Int.busNumberFormatted(context: Context) = context.getString(R.string.city_bus_number_format, this)
\ No newline at end of file
diff --git a/data/src/main/res/values/strings.xml b/data/src/main/res/values/strings.xml
index 6d5829aed..a9aeb0cd8 100644
--- a/data/src/main/res/values/strings.xml
+++ b/data/src/main/res/values/strings.xml
@@ -26,33 +26,6 @@
성별을 체크해 주세요
로그아웃에 실패했습니다.
-
- 금일 운영 종료 되었습니다.
- 시간표(터미널)
- 6:00(첫) - 22:30(막) (10분간격)
- 시간표(병천)
- 6:10(첫) - 22:45(막) (10분간격)
- 소요시간
- 약 40분
- 셔틀 등교
- 셔틀 하교
- 등교
- 하교
-
-
- H시간 mm분 ss초 전
- 운행정보없음
- H:mm 출발
- 한기대
- 천안역
- 야우리
- 시내버스
- 통학버스
- 대성고속
- 셔틀버스
- 학교셔틀
- %1$d번 버스
-
기계공학부
메카트로닉스공학부
diff --git a/domain/src/main/java/in/koreatech/koin/domain/error/bus/BusErrorHandler.kt b/domain/src/main/java/in/koreatech/koin/domain/error/bus/BusErrorHandler.kt
deleted file mode 100644
index 1ac70104f..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/error/bus/BusErrorHandler.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package `in`.koreatech.koin.domain.error.bus
-
-import `in`.koreatech.koin.domain.model.bus.*
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-
-interface BusErrorHandler {
- fun handleGetBusCoursesError(throwable: Throwable) : ErrorHandler
- fun handleGetBusTimetableError(throwable: Throwable) : ErrorHandler
- fun handleGetBusRemainTimeError(throwable: Throwable) : ErrorHandler
- fun handleGetBusCoursesErrorHandler(throwable: Throwable): ErrorHandler
- fun handleSearchBusError(throwable: Throwable) : ErrorHandler
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/error/busv2/ErrorType.kt b/domain/src/main/java/in/koreatech/koin/domain/error/busv2/ErrorType.kt
deleted file mode 100644
index 6d260a619..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/error/busv2/ErrorType.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package `in`.koreatech.koin.domain.error.busv2
-
-sealed class SearchBusError: Throwable() {
- class EmptyDeparture: SearchBusError()
- class EmptyArrival: SearchBusError()
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusDirection.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusDirection.kt
deleted file mode 100644
index 5f532f23f..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusDirection.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus
-
-sealed class BusDirection(
- val busDirectionString: String
-) {
- object ToKoreatech: BusDirection("to") // 학교로
- object FromKoreatech: BusDirection("from") // 학교에서
-}
-
-fun String.toBusDirection() = when(this) {
- "to" -> BusDirection.ToKoreatech
- "from" -> BusDirection.FromKoreatech
- else -> throw IllegalArgumentException("Not a bus node string.")
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNode.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNode.kt
deleted file mode 100644
index ec2926574..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNode.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus
-
-
-sealed class BusNode(
- val busNodeString: String
-) {
- object Koreatech: BusNode("koreatech")
- object Station: BusNode("station")
- object Terminal: BusNode("terminal")
-}
-
-fun String.toBusNode() = when(this) {
- "koreatech" -> BusNode.Koreatech
- "station" -> BusNode.Station
- "terminal" -> BusNode.Terminal
- else -> throw IllegalArgumentException("Not a bus node string.")
-}
-
-val BusNode.spinnerSelection
- get() = when (this) {
- BusNode.Koreatech -> 0
- BusNode.Station -> 2
- BusNode.Terminal -> 1
- }
-
-val Int.busNodeSelection
- get() = when (this) {
- 0 -> BusNode.Koreatech
- 2 -> BusNode.Station
- 1 -> BusNode.Terminal
- else -> throw IllegalArgumentException("Not supported selection.")
- }
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNotice.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNotice.kt
new file mode 100644
index 000000000..866b2291a
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusNotice.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class BusNotice(
+ val id: Int,
+ val title: String
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusRunningDay.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusRunningDay.kt
deleted file mode 100644
index f1460ed5a..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusRunningDay.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus
-
-sealed class BusRunningDay {
- object SUN : BusRunningDay()
- object MON : BusRunningDay()
- object TUE : BusRunningDay()
- object WED : BusRunningDay()
- object THU : BusRunningDay()
- object FRI : BusRunningDay()
- object SAT : BusRunningDay()
-}
-
-fun Iterable.toBusRunningDays() = this.mapIndexedNotNull { index, i ->
- if (i == 1) when (index) {
- 0 -> BusRunningDay.SUN
- 1 -> BusRunningDay.MON
- 2 -> BusRunningDay.TUE
- 3 -> BusRunningDay.WED
- 4 -> BusRunningDay.THU
- 5 -> BusRunningDay.FRI
- 6 -> BusRunningDay.SAT
- else -> null
- } else null
-}
-
-fun Iterable.toIntList(): List {
- val result = MutableList(7) { 0 }
- this.forEach {
- result[when (it) {
- BusRunningDay.SUN -> 0
- BusRunningDay.MON -> 1
- BusRunningDay.TUE -> 2
- BusRunningDay.WED -> 3
- BusRunningDay.THU -> 4
- BusRunningDay.FRI -> 5
- BusRunningDay.SAT -> 6
- }] = 1
- }
-
- return result
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusSearchResult.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusSearchResult.kt
new file mode 100644
index 000000000..8f7c2ea44
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusSearchResult.kt
@@ -0,0 +1,9 @@
+package `in`.koreatech.koin.domain.model.bus
+
+import java.time.LocalTime
+
+data class BusSearchResult(
+ val busType: String,
+ val busName: String,
+ val departureTime: LocalTime
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusType.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusType.kt
deleted file mode 100644
index c237dadf6..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/BusType.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus
-
-sealed class BusType(
- val busTypeString: String
-) {
- object Shuttle : BusType("shuttle")
- object Commuting : BusType("commuting")
- object Express : BusType("express")
- object City : BusType("city")
-}
-
-fun String.toBusType() = when (this) {
- "shuttle" -> BusType.Shuttle
- "commuting" -> BusType.Commuting
- "express" -> BusType.Express
- "city" -> BusType.City
- else -> throw IllegalArgumentException("Not a bus type string")
-}
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityBusInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityBusInfo.kt
new file mode 100644
index 000000000..eb1f01cee
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityBusInfo.kt
@@ -0,0 +1,7 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class CityBusInfo(
+ val number: Int,
+ val departNode: String,
+ val arriveNode: String,
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetable.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetable.kt
new file mode 100644
index 000000000..8c7f57690
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetable.kt
@@ -0,0 +1,9 @@
+package `in`.koreatech.koin.domain.model.bus
+
+import java.time.LocalDateTime
+
+data class CityTimetable(
+ val timetable: List,
+ val busInfo: CityBusInfo,
+ val updatedAt: LocalDateTime
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetableItem.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetableItem.kt
new file mode 100644
index 000000000..f3167ec82
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/CityTimetableItem.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class CityTimetableItem(
+ val dayOfWeek: String,
+ val departureTimes: List
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetable.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetable.kt
new file mode 100644
index 000000000..8d60c78d3
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetable.kt
@@ -0,0 +1,8 @@
+package `in`.koreatech.koin.domain.model.bus
+
+import java.time.LocalDateTime
+
+data class ExpressTimetable(
+ val timetable: List,
+ val updatedAt: LocalDateTime,
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetableItem.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetableItem.kt
new file mode 100644
index 000000000..3887534fd
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ExpressTimetableItem.kt
@@ -0,0 +1,7 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ExpressTimetableItem(
+ val arrivalTime: String,
+ val departureTime: String,
+ val charge: Int,
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourse.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourse.kt
new file mode 100644
index 000000000..4b803f5ce
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourse.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleCourse(
+ val region: String,
+ val routes: List
+)
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourseRoute.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourseRoute.kt
new file mode 100644
index 000000000..031c808b8
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourseRoute.kt
@@ -0,0 +1,8 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleCourseRoute(
+ val id: String,
+ val type: String,
+ val routeName: String,
+ val subName: String,
+)
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourses.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourses.kt
new file mode 100644
index 000000000..5b849074d
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleCourses.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleCourses(
+ val courses: List,
+ val semester: ShuttleSemester
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleSemester.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleSemester.kt
new file mode 100644
index 000000000..b3c14a119
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleSemester.kt
@@ -0,0 +1,9 @@
+package `in`.koreatech.koin.domain.model.bus
+
+import java.time.LocalDate
+
+data class ShuttleSemester(
+ val name: String,
+ val from: LocalDate,
+ val to: LocalDate
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetable.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetable.kt
new file mode 100644
index 000000000..099e3f585
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetable.kt
@@ -0,0 +1,10 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleTimetable(
+ val region: String,
+ val routeType: String,
+ val routeName: String,
+ val subTitle: String,
+ val nodeInfo: List,
+ val routeInfo: List,
+)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableNodeInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableNodeInfo.kt
new file mode 100644
index 000000000..79d146fd8
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableNodeInfo.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleTimetableNodeInfo(
+ val name: String,
+ val detail: String,
+)
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableRouteInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableRouteInfo.kt
new file mode 100644
index 000000000..62e7b5afe
--- /dev/null
+++ b/domain/src/main/java/in/koreatech/koin/domain/model/bus/ShuttleTimetableRouteInfo.kt
@@ -0,0 +1,6 @@
+package `in`.koreatech.koin.domain.model.bus
+
+data class ShuttleTimetableRouteInfo(
+ val name: String,
+ val arrivalTimes: List,
+)
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusDayType.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusDayType.kt
deleted file mode 100644
index 05d1019bb..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusDayType.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.city
-
-import java.time.DayOfWeek
-
-enum class CityBusDayType(
- val type: String,
- // TODO: 공휴일, 임시 조건 추가하면서 타입 변경 필요
- val daysOfWeek: List
-) {
- Weekday("평일", listOf(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY)),
- Weekend("주말", listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)),
- // TODO: 조건 추가
- Holiday("공휴일", emptyList()),
- Temp("임시", emptyList())
-}
-
-val String.toCityBusDayType
- get() = when (this) {
- "평일" -> CityBusDayType.Weekday
- "주말" -> CityBusDayType.Weekend
- "공휴일" -> CityBusDayType.Holiday
- "임시" -> CityBusDayType.Temp
- else -> throw IllegalArgumentException("Invalid day type")
- }
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusGeneralDestination.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusGeneralDestination.kt
deleted file mode 100644
index adae6caf2..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusGeneralDestination.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.city
-
-enum class CityBusGeneralDestination(
- val destination: String
-) {
- Terminal("터미널"),
- Beongchon("병천")
-}
-
-val CityBusGeneralDestination.toggleSelection
- get() = when (this) {
- CityBusGeneralDestination.Beongchon -> 0
- CityBusGeneralDestination.Terminal -> 1
- }
-
-val Int.toCityBusGeneralDestination
- get() = when (this) {
- 0 -> CityBusGeneralDestination.Beongchon
- 1 -> CityBusGeneralDestination.Terminal
- else -> throw IllegalArgumentException("Not supported selection.")
- }
-
-val String.toCityBusGeneralDestination
- get() = when (this) {
- CityBusSpecificDestination.Terminal.destination -> CityBusGeneralDestination.Terminal
- CityBusSpecificDestination.Beongchon400.destination,
- CityBusSpecificDestination.Beongchon402.destination,
- CityBusSpecificDestination.Beongchon405.destination -> CityBusGeneralDestination.Beongchon
- else -> throw IllegalArgumentException("Not supported node string")
- }
-
-val CityBusSpecificDestination.toCityBusGeneralDestination
- get() = when (this) {
- CityBusSpecificDestination.Terminal -> CityBusGeneralDestination.Terminal
- CityBusSpecificDestination.Beongchon400,
- CityBusSpecificDestination.Beongchon402,
- CityBusSpecificDestination.Beongchon405 -> CityBusGeneralDestination.Beongchon
- }
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusInfo.kt
deleted file mode 100644
index 558933e13..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusInfo.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.city
-
-data class CityBusInfo(
- val busNumber: Int,
- val direction: String
-)
-
-fun Pair.toCityBusInfo(): CityBusInfo {
- val (busNumber, destination) = this
- val direction = destination.toCityBusSpecificDestination(busNumber).destination
- return CityBusInfo(busNumber.number, direction)
-}
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusNumber.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusNumber.kt
deleted file mode 100644
index 69283ac30..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusNumber.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.city
-
-enum class CityBusNumber(
- val number: Int
-) {
- Bus400(400),
- Bus402(402),
- Bus405(405)
-}
-
-val CityBusNumber.spinnerSelection
- get() = when (this) {
- CityBusNumber.Bus400 -> 0
- CityBusNumber.Bus402 -> 1
- CityBusNumber.Bus405 -> 2
- }
-
-val Int.posToCityBusNumber
- get() = when (this) {
- 0 -> CityBusNumber.Bus400
- 1 -> CityBusNumber.Bus402
- 2 -> CityBusNumber.Bus405
- else -> throw IllegalArgumentException("Not supported selection.")
- }
-
-val Int.numberToCityBusNumber
- get() = when (this) {
- 400 -> CityBusNumber.Bus400
- 402 -> CityBusNumber.Bus402
- 405 -> CityBusNumber.Bus405
- else -> throw IllegalArgumentException("Not supported selection.")
- }
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusSpecificDestination.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusSpecificDestination.kt
deleted file mode 100644
index d28c832d8..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/city/CityBusSpecificDestination.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.city
-
-enum class CityBusSpecificDestination(
- val destination: String
-) {
- Terminal("종합터미널"),
- Beongchon400("병천3리"),
- Beongchon402("황사동"),
- Beongchon405("유관순열사사적지")
-}
-
-fun CityBusGeneralDestination.toCityBusSpecificDestination(number: CityBusNumber): CityBusSpecificDestination {
- when (this) {
- CityBusGeneralDestination.Terminal -> return CityBusSpecificDestination.Terminal
- CityBusGeneralDestination.Beongchon -> {
- when (number) {
- CityBusNumber.Bus400 -> return CityBusSpecificDestination.Beongchon400
- CityBusNumber.Bus402 -> return CityBusSpecificDestination.Beongchon402
- CityBusNumber.Bus405 -> return CityBusSpecificDestination.Beongchon405
- }
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/course/BusCourse.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/course/BusCourse.kt
deleted file mode 100644
index f1326e30f..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/course/BusCourse.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.course
-
-import `in`.koreatech.koin.domain.model.bus.BusDirection
-import `in`.koreatech.koin.domain.model.bus.BusType
-
-data class BusCourse(
- val busType: BusType,
- val direction: BusDirection,
- val region: String
-)
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/search/BusSearchResult.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/search/BusSearchResult.kt
deleted file mode 100644
index 60c1845af..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/search/BusSearchResult.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.search
-
-import `in`.koreatech.koin.domain.model.bus.BusType
-import java.time.LocalDateTime
-
-data class BusSearchResult(
- val busType: BusType,
- val busTimeString: String
-)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusArrivalInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusArrivalInfo.kt
deleted file mode 100644
index 0c8d1d826..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusArrivalInfo.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.timer
-
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.BusType
-import java.time.LocalTime
-
-sealed class BusArrivalInfo(
- open val departure: BusNode,
- open val arrival: BusNode,
- open val nowBusRemainTime: Long?,
- open val nextBusRemainTime: Long?,
- open val nowBusArrivalTime: LocalTime?,
- open val nextBusArrivalTime: LocalTime?,
- open val criteria: LocalTime
-) {
- class ShuttleBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode,
- nowBusRemainTime: Long?,
- nextBusRemainTime: Long?,
- nowBusArrivalTime: LocalTime?,
- nextBusArrivalTime: LocalTime?,
- criteria: LocalTime
- ) : BusArrivalInfo(
- departure,
- arrival,
- nowBusRemainTime,
- nextBusRemainTime,
- nowBusArrivalTime,
- nextBusArrivalTime,
- criteria
- )
-
- class CommutingBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode,
- nowBusRemainTime: Long?,
- nextBusRemainTime: Long?,
- nowBusArrivalTime: LocalTime?,
- nextBusArrivalTime: LocalTime?,
- criteria: LocalTime
- ) : BusArrivalInfo(
- departure,
- arrival,
- nowBusRemainTime,
- nextBusRemainTime,
- nowBusArrivalTime,
- nextBusArrivalTime,
- criteria
- )
-
- class ExpressBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode,
- nowBusRemainTime: Long?,
- nextBusRemainTime: Long?,
- nowBusArrivalTime: LocalTime?,
- nextBusArrivalTime: LocalTime?,
- criteria: LocalTime
- ) : BusArrivalInfo(
- departure,
- arrival,
- nowBusRemainTime,
- nextBusRemainTime,
- nowBusArrivalTime,
- nextBusArrivalTime,
- criteria
- )
-
- class CityBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode,
- nowBusRemainTime: Long?,
- nextBusRemainTime: Long?,
- nowBusArrivalTime: LocalTime?,
- nextBusArrivalTime: LocalTime?,
- criteria: LocalTime,
- val busNumber: Int
- ) : BusArrivalInfo(
- departure,
- arrival,
- nowBusRemainTime,
- nextBusRemainTime,
- nowBusArrivalTime,
- nextBusArrivalTime,
- criteria
- )
-}
-
-val BusArrivalInfo.busType
- get() = when (this) {
- is BusArrivalInfo.CityBusArrivalInfo -> BusType.City
- is BusArrivalInfo.CommutingBusArrivalInfo -> BusType.Commuting
- is BusArrivalInfo.ExpressBusArrivalInfo -> BusType.Express
- is BusArrivalInfo.ShuttleBusArrivalInfo -> BusType.Shuttle
- }
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusRemainTime.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusRemainTime.kt
deleted file mode 100644
index 4570f6941..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timer/BusRemainTime.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.timer
-
-sealed class BusRemainTime {
- data class CityBusRemainTime(
- val busNumber: Int,
- val remainTimeSeconds: Long
- ) : BusRemainTime()
-
- data class ShuttleBusRemainTime(
- val remainTimeSeconds: Long
- ) : BusRemainTime()
-
- data class ExpressBusRemainTime(
- val remainTimeSeconds: Long
- ) : BusRemainTime()
-
- data class CommutingBusRemainTime(
- val remainTimeSeconds: Long
- ) : BusRemainTime()
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusNodeInfo.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusNodeInfo.kt
deleted file mode 100644
index fa234ab8b..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusNodeInfo.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.timetable
-
-import `in`.koreatech.koin.domain.model.bus.city.CityBusGeneralDestination
-import `in`.koreatech.koin.domain.model.bus.city.CityBusNumber
-import java.time.LocalTime
-
-sealed class BusNodeInfo {
- data class ExpressNodeInfo(
- val departureTime: String,
- val arrivalTime: String,
- val charge: Int
- ) : BusNodeInfo()
-
- data class ShuttleNodeInfo(
- val node: String,
- val arrivalTime: String
- ): BusNodeInfo()
-
- data class CityBusNodeInfo(
- val busNumber: CityBusNumber,
- val departNode: CityBusGeneralDestination,
- val arrivalNode: CityBusGeneralDestination
- ): BusNodeInfo()
-}
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusRoute.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusRoute.kt
deleted file mode 100644
index b127c9c31..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusRoute.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.timetable
-
-import `in`.koreatech.koin.domain.model.bus.BusDirection
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-
-sealed class BusRoute {
- data class ShuttleBusRoute(
- val routeName: String,
- val arrivalInfo: List
- ): BusRoute()
-
- data class ExpressBusRoute(
- val arrivalInfo: List
- ): BusRoute()
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusTimetable.kt b/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusTimetable.kt
deleted file mode 100644
index 2f6ba9a79..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/model/bus/timetable/BusTimetable.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package `in`.koreatech.koin.domain.model.bus.timetable
-
-sealed class BusTimetable {
- data class ShuttleBusTimetable(
- val routes: List,
- val updatedAt: String
- ) : BusTimetable()
-
- data class ExpressBusTimetable(
- val routes: BusRoute.ExpressBusRoute,
- val updatedAt: String
- ) : BusTimetable()
-
- data class CityBusTimetable(
- val busInfos: BusNodeInfo.CityBusNodeInfo,
- val departTimes: List, // 평일 or 주말 or 공휴일 or 임시 출발시간 리스트
- val updatedAt: String
- ): BusTimetable()
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/BusRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/BusRepository.kt
index aa98c360a..596cca848 100644
--- a/domain/src/main/java/in/koreatech/koin/domain/repository/BusRepository.kt
+++ b/domain/src/main/java/in/koreatech/koin/domain/repository/BusRepository.kt
@@ -1,44 +1,28 @@
package `in`.koreatech.koin.domain.repository
-import `in`.koreatech.koin.domain.model.bus.*
-import `in`.koreatech.koin.domain.model.bus.city.CityBusInfo
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusRoute
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import java.time.LocalDateTime
+import `in`.koreatech.koin.domain.model.bus.BusNotice
+import `in`.koreatech.koin.domain.model.bus.BusSearchResult
+import `in`.koreatech.koin.domain.model.bus.CityTimetable
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetable
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourses
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetable
+import java.time.LocalDate
+import java.time.LocalTime
interface BusRepository {
- suspend fun getShuttleBusCourses(): List>
- suspend fun getExpressBusCourses(): List>
- suspend fun getShuttleBusTimetable(busCourse: BusCourse): BusTimetable.ShuttleBusTimetable
- suspend fun getExpressBusTimetable(busCourse: BusCourse): BusTimetable.ExpressBusTimetable
- suspend fun getCityBusTimetable(cityBusInfo: CityBusInfo): BusTimetable.CityBusTimetable
+ suspend fun fetchBusNotice(): Result
+ suspend fun fetchShuttleTimetable(id: String): Result
+ suspend fun fetchShuttleCourses(): Result
+ suspend fun fetchExpressTimetable(direction: String): Result
+ suspend fun fetchCityTimetable(number: Int, direction: String): Result
+ suspend fun fetchBusSearchResult(
+ date: LocalDate,
+ time: LocalTime,
+ busType: String,
+ departure: String,
+ arrival: String
+ ): Result>
- suspend fun getShuttleBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.ShuttleBusArrivalInfo
-
- suspend fun getExpressBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.ExpressBusArrivalInfo
-
- suspend fun getCommutingBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.CommutingBusArrivalInfo
-
- suspend fun getCityBusRemainTime(
- departure: BusNode,
- arrival: BusNode
- ): BusArrivalInfo.CityBusArrivalInfo
-
- suspend fun searchBus(
- time: LocalDateTime,
- departure: BusNode,
- arrival: BusNode
- ): List
+ suspend fun getLastShownNoticeId(): Result
+ suspend fun saveLastShownNoticeId(id: Int): Result
}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt
index 35a02fba9..afd433ac2 100644
--- a/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt
+++ b/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt
@@ -17,6 +17,7 @@ interface UserRepository {
): AuthToken
fun ownerTokenIsValid(): Boolean
+ suspend fun fetchUserInfo()
suspend fun getUserInfo(): User
fun getUserInfoFlow(): Flow
suspend fun requestPasswordResetEmail(email: String)
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/search/SearchBusUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/search/SearchBusUseCase.kt
deleted file mode 100644
index 2bdde8389..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/search/SearchBusUseCase.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.search
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import java.time.LocalDateTime
-import javax.inject.Inject
-
-class SearchBusUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(
- dateTime: LocalDateTime,
- departure: BusNode,
- arrival: BusNode
- ): Pair?, ErrorHandler?> {
- return try {
- busRepository.searchBus(dateTime, departure, arrival) to null
- } catch (t: Throwable) {
- null to busErrorHandler.handleSearchBusError(t)
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timer/GetBusTimerUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timer/GetBusTimerUseCase.kt
deleted file mode 100644
index 858c248ff..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timer/GetBusTimerUseCase.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timer
-
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.domain.repository.BusRepository
-import `in`.koreatech.koin.domain.util.ext.second
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.map
-import java.time.Duration
-import java.time.LocalTime
-import java.time.ZoneId
-import javax.inject.Inject
-
-class GetBusTimerUseCase @Inject constructor(
- private val busRepository: BusRepository
-) {
- operator fun invoke(
- departure: BusNode,
- arrival: BusNode
- ) = flow {
- var count = 1L
- val period = 1.second
- var time = System.currentTimeMillis() - 1.second
- var list = getBusArrivalInfo(departure, arrival)
-
- if (departure != arrival) {
- while (true) {
- System.currentTimeMillis().let {
- if (it - time >= period) {
-
- if (count / 60 > 0 || count == 0L) {
- list = getBusArrivalInfo(departure, arrival)
- count = 0
- }
-
- val nowLocalTime = LocalTime.now(ZoneId.of("Asia/Seoul"))
-
- time += period
- count++
-
- val result = list.mapNotNull { busArrivalInfo ->
- when (busArrivalInfo) {
- is BusArrivalInfo.CityBusArrivalInfo -> BusArrivalInfo.CityBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = busArrivalInfo.nowBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nextBusRemainTime = busArrivalInfo.nextBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nowBusArrivalTime = busArrivalInfo.nowBusArrivalTime,
- nextBusArrivalTime = busArrivalInfo.nextBusArrivalTime,
- criteria = busArrivalInfo.criteria,
- busNumber = busArrivalInfo.busNumber
- )
- is BusArrivalInfo.ExpressBusArrivalInfo ->
- BusArrivalInfo.ExpressBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = busArrivalInfo.nowBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nextBusRemainTime = busArrivalInfo.nextBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nowBusArrivalTime = busArrivalInfo.nowBusArrivalTime,
- nextBusArrivalTime = busArrivalInfo.nextBusArrivalTime,
- criteria = busArrivalInfo.criteria
- )
-
- is BusArrivalInfo.ShuttleBusArrivalInfo ->
- BusArrivalInfo.ShuttleBusArrivalInfo(
- departure = departure,
- arrival = arrival,
- nowBusRemainTime = busArrivalInfo.nowBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nextBusRemainTime = busArrivalInfo.nextBusRemainTime?.let {
- getDurationOrReset(
- it,
- busArrivalInfo.criteria,
- nowLocalTime
- ) { count = 0L }
- },
- nowBusArrivalTime = busArrivalInfo.nowBusArrivalTime,
- nextBusArrivalTime = busArrivalInfo.nextBusArrivalTime,
- criteria = busArrivalInfo.criteria
- )
- is BusArrivalInfo.CommutingBusArrivalInfo -> null
- }
- }
-
- emit(result)
- } else delay(period / 100)
- }
- }
- }
- }
-
- private suspend fun getBusArrivalInfo(
- departure: BusNode,
- arrival: BusNode
- ): List = mutableListOf().apply {
- add(busRepository.getShuttleBusRemainTime(departure, arrival))
- if (departure != BusNode.Station && arrival != BusNode.Station) {
- add(busRepository.getExpressBusRemainTime(departure, arrival))
- }
- add(busRepository.getCityBusRemainTime(departure, arrival))
- }
-
- private inline fun getDurationOrReset(
- busRemainTime: Long,
- localTimeCriteria: LocalTime,
- localTimeNow: LocalTime,
- onTimeEnd: () -> Unit
- ): Long {
- return (busRemainTime - Duration.between(localTimeCriteria, localTimeNow).seconds).let {
- if (it < 0) {
- onTimeEnd()
- 0
- } else {
- it
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/city/GetCityBusTimetableUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/city/GetCityBusTimetableUseCase.kt
deleted file mode 100644
index eb8515b21..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/city/GetCityBusTimetableUseCase.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timetable.city
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.city.CityBusInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import javax.inject.Inject
-
-class GetCityBusTimetableUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(cityBusInfo: CityBusInfo) : Pair {
- return try {
- busRepository.getCityBusTimetable(cityBusInfo) to null
- } catch (t: Throwable) {
- null to busErrorHandler.handleGetBusTimetableError(t)
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusCoursesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusCoursesUseCase.kt
deleted file mode 100644
index c1556be70..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusCoursesUseCase.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timetable.express
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import javax.inject.Inject
-
-class GetExpressBusCoursesUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(): Pair>?, ErrorHandler?> {
- return try {
- busRepository.getExpressBusCourses() to null
- } catch (t: Throwable) {
- null to busErrorHandler.handleGetBusTimetableError(t)
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusTimetableUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusTimetableUseCase.kt
deleted file mode 100644
index 44c46a34f..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/express/GetExpressBusTimetableUseCase.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timetable.express
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import javax.inject.Inject
-
-class GetExpressBusTimetableUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(busCourse: BusCourse): Pair {
- return try {
- busRepository.getExpressBusTimetable(busCourse) to null
- } catch (t: Throwable) {
- null to busErrorHandler.handleGetBusTimetableError(t)
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusCoursesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusCoursesUseCase.kt
deleted file mode 100644
index cac44f0c0..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusCoursesUseCase.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timetable.shuttle
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import javax.inject.Inject
-
-class GetShuttleBusCoursesUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(): Pair>?, ErrorHandler?> {
- val sort = listOf("천안", "청주", "서울", "대전", "세종")
- return try {
- busRepository.getShuttleBusCourses()
- .sortedWith { p0, p1 ->
- (sort.indexOf(p0.first.region) - sort.indexOf(p1.first.region)).let {
- if (it == 0) {
- p0.first.busType.importance - p1.first.busType.importance
- } else it
- }
- } to null
- } catch (t: Throwable) {
- null to busErrorHandler.handleGetBusCoursesError(t)
- }
- }
-}
-
-private val BusType.importance get() = when(this) {
- BusType.City -> 4
- BusType.Commuting -> 1
- BusType.Express -> 3
- BusType.Shuttle -> 2
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusTimetableUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusTimetableUseCase.kt
deleted file mode 100644
index d8a6bcb1b..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/bus/timetable/shuttle/GetShuttleBusTimetableUseCase.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.bus.timetable.shuttle
-
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import `in`.koreatech.koin.domain.model.error.ErrorHandler
-import `in`.koreatech.koin.domain.repository.BusRepository
-import javax.inject.Inject
-
-class GetShuttleBusTimetableUseCase @Inject constructor(
- private val busRepository: BusRepository,
- private val busErrorHandler: BusErrorHandler
-) {
- suspend operator fun invoke(
- busCourse: BusCourse,
- ): Pair {
- return try {
- busRepository.getShuttleBusTimetable(busCourse) to null
- } catch(t: Throwable) {
- null to busErrorHandler.handleGetBusTimetableError(t)
- }
- }
-}
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/busv2/SearchBusV2UseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/busv2/SearchBusV2UseCase.kt
deleted file mode 100644
index af90e655e..000000000
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/busv2/SearchBusV2UseCase.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package `in`.koreatech.koin.domain.usecase.busv2
-
-import `in`.koreatech.koin.domain.error.busv2.SearchBusError
-import javax.inject.Inject
-
-class SearchBusV2UseCase @Inject constructor(
- // private val busRepository
-) {
-
- suspend operator fun invoke(departure: String, arrival: String): Result {
- return when {
- departure.isEmpty() -> Result.failure(SearchBusError.EmptyDeparture())
- arrival.isEmpty() -> Result.failure(SearchBusError.EmptyArrival())
- else -> {
- // TODO repository Search bus
- // busRepository.searchBus(departure, arrival)
- Result.success(Unit)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/user/UserLoginUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/user/UserLoginUseCase.kt
index a975ea666..f6e35ed2c 100644
--- a/domain/src/main/java/in/koreatech/koin/domain/usecase/user/UserLoginUseCase.kt
+++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/user/UserLoginUseCase.kt
@@ -20,6 +20,7 @@ class UserLoginUseCase @Inject constructor(
val authToken = userRepository.getToken(email, password.toSHA256())
tokenRepository.saveAccessToken(authToken.token)
tokenRepository.saveRefreshToken(authToken.refreshToken)
+ userRepository.fetchUserInfo()
Unit to null
} catch (throwable: Throwable) {
null to userErrorHandler.handleGetTokenError(throwable)
diff --git a/feature/bus/build.gradle.kts b/feature/bus/build.gradle.kts
index e36f7f0cf..117d2b1d5 100644
--- a/feature/bus/build.gradle.kts
+++ b/feature/bus/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
implementation(project(":domain"))
implementation(project(":core:onboarding"))
implementation(project(":core:designsystem"))
+ implementation(project(":core:analytics"))
implementation(libs.core.ktx)
implementation(libs.appcompat)
@@ -40,4 +41,6 @@ dependencies {
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.lottie.compose)
+
+ implementation(libs.timber)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/AndroidManifest.xml b/feature/bus/src/main/AndroidManifest.xml
index 3aa47a2bd..b7f8f1694 100644
--- a/feature/bus/src/main/AndroidManifest.xml
+++ b/feature/bus/src/main/AndroidManifest.xml
@@ -4,10 +4,12 @@
diff --git a/feature/bus/src/main/java/in/koreatech/bus/BaseBusViewModel.kt b/feature/bus/src/main/java/in/koreatech/bus/BaseBusViewModel.kt
new file mode 100644
index 000000000..3085e52e3
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/BaseBusViewModel.kt
@@ -0,0 +1,15 @@
+package `in`.koreatech.bus
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+abstract class BaseBusViewModel: ViewModel() {
+
+ private val _refreshToggle = MutableStateFlow(false)
+ protected val refreshToggle = _refreshToggle.asStateFlow()
+
+ open fun refresh() {
+ _refreshToggle.value = !_refreshToggle.value
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/BusSearchActivity.kt b/feature/bus/src/main/java/in/koreatech/bus/BusSearchActivity.kt
index ba447d432..aeb9da770 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/BusSearchActivity.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/BusSearchActivity.kt
@@ -1,9 +1,9 @@
package `in`.koreatech.bus
import android.os.Bundle
+import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
@@ -11,7 +11,7 @@ import `in`.koreatech.bus.navigation.BusSearchNavigation
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
@AndroidEntryPoint
-class BusSearchActivity : AppCompatActivity() {
+class BusSearchActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
diff --git a/feature/bus/src/main/java/in/koreatech/bus/BusTimetableActivity.kt b/feature/bus/src/main/java/in/koreatech/bus/BusTimetableActivity.kt
index 8ae6cc9d1..1906b57bc 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/BusTimetableActivity.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/BusTimetableActivity.kt
@@ -1,9 +1,9 @@
package `in`.koreatech.bus
import android.os.Bundle
+import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
@@ -11,7 +11,7 @@ import `in`.koreatech.bus.navigation.BusTimetableNavigation
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
@AndroidEntryPoint
-class BusTimetableActivity : AppCompatActivity() {
+class BusTimetableActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
diff --git a/feature/bus/src/main/java/in/koreatech/bus/animation/NavigationAnimation.kt b/feature/bus/src/main/java/in/koreatech/bus/animation/NavigationAnimation.kt
new file mode 100644
index 000000000..4cc01bc59
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/animation/NavigationAnimation.kt
@@ -0,0 +1,51 @@
+package `in`.koreatech.bus.animation
+
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.ui.graphics.Color
+
+private const val TRANSITION_DURATION = 400
+private const val FADE_RATIO = .55f
+private const val SCALE_RATIO = .9f
+private const val SLIDE_DIVIDE_RATIO = 3
+
+internal val defaultOutsideColor = Color(0xFF282828)
+
+internal fun AnimatedContentTransitionScope.defaultEnterTransition() = slideInHorizontally(
+ animationSpec = tween(TRANSITION_DURATION),
+ initialOffsetX = { it }
+)
+
+internal fun AnimatedContentTransitionScope.defaultExitTransition() = fadeOut(
+ animationSpec = tween(TRANSITION_DURATION),
+ targetAlpha = FADE_RATIO
+) + scaleOut(
+ animationSpec = tween(TRANSITION_DURATION),
+ targetScale = SCALE_RATIO
+) + slideOutOfContainer(
+ towards = AnimatedContentTransitionScope.SlideDirection.Left,
+ animationSpec = tween(TRANSITION_DURATION),
+ targetOffset = { it / SLIDE_DIVIDE_RATIO }
+)
+
+internal fun AnimatedContentTransitionScope.defaultPopEnterTransition() = fadeIn(
+ animationSpec = tween(TRANSITION_DURATION),
+ initialAlpha = FADE_RATIO
+) + scaleIn(
+ animationSpec = tween(TRANSITION_DURATION),
+ initialScale = SCALE_RATIO
+) + slideIntoContainer(
+ towards = AnimatedContentTransitionScope.SlideDirection.Right,
+ animationSpec = tween(TRANSITION_DURATION),
+ initialOffset = { it / SLIDE_DIVIDE_RATIO }
+)
+
+internal fun AnimatedContentTransitionScope.defaultPopExitTransition() = slideOutOfContainer(
+ towards = AnimatedContentTransitionScope.SlideDirection.Right,
+ animationSpec = tween(TRANSITION_DURATION),
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/animation/Skeleton.kt b/feature/bus/src/main/java/in/koreatech/bus/animation/Skeleton.kt
new file mode 100644
index 000000000..012992c5a
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/animation/Skeleton.kt
@@ -0,0 +1,62 @@
+package `in`.koreatech.bus.animation
+
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+
+internal object Skeleton {
+ val brush: Brush
+ @Composable
+ get() = getCommonSkeletonBrush()
+
+ val shape: Shape = RoundedCornerShape(4.dp)
+}
+
+@Composable
+internal fun Modifier.skeleton(
+ brush: Brush = Skeleton.brush,
+ shape: Shape = Skeleton.shape
+) = this.background(
+ brush = brush,
+ shape = shape
+)
+
+@Composable
+internal fun getCommonSkeletonBrush() : Brush {
+ val skeletonColors = listOf(
+ Color.LightGray.copy(alpha = 0.6f),
+ Color.LightGray.copy(alpha = 0.2f),
+ Color.LightGray.copy(alpha = 0.6f),
+ )
+
+ val transition = rememberInfiniteTransition()
+ val translateAnim = transition.animateFloat(
+ initialValue = 0f,
+ targetValue = 1000f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(
+ durationMillis = 1000,
+ easing = FastOutSlowInEasing
+ ),
+ repeatMode = RepeatMode.Reverse
+ )
+ )
+
+ return Brush.linearGradient(
+ colors = skeletonColors,
+ start = Offset.Zero,
+ end = Offset(x = translateAnim.value, y = translateAnim.value)
+ )
+}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/BusTypeChip.kt b/feature/bus/src/main/java/in/koreatech/bus/component/BusTypeChip.kt
new file mode 100644
index 000000000..f2a684f01
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/BusTypeChip.kt
@@ -0,0 +1,50 @@
+package `in`.koreatech.bus.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.sp
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.koin.core.designsystem.component.chip.ReadOnlyTextChip
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+
+@Composable
+internal fun BusTypeChip(
+ busType: BusType,
+ modifier: Modifier = Modifier
+) {
+ ReadOnlyTextChip(
+ modifier = modifier,
+ title = stringResource(busType.titleRes),
+ containerColor = when (busType) {
+ BusType.SHUTTLE -> Color(0xFFFBEBD7)
+ BusType.EXPRESS -> Color(0xFFD7E6FB)
+ BusType.CITY -> Color(0xFFD7FBEB)
+ else -> Color.Transparent
+ },
+ textStyle = KoinTheme.typography.regular12.copy(
+ color = KoinTheme.colors.neutral600,
+ fontSize = 11.sp
+ )
+ )
+}
+
+@Preview
+@Composable
+private fun BusTypeChipPreview() {
+ BusTypeChip(busType = BusType.SHUTTLE)
+}
+
+@Preview
+@Composable
+private fun BusTypeChipPreview2() {
+ BusTypeChip(busType = BusType.EXPRESS)
+}
+
+@Preview
+@Composable
+private fun BusTypeChipPreview3() {
+ BusTypeChip(busType = BusType.CITY)
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/CommonEmptyView.kt b/feature/bus/src/main/java/in/koreatech/bus/component/CommonEmptyView.kt
new file mode 100644
index 000000000..6e7015bcd
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/CommonEmptyView.kt
@@ -0,0 +1,51 @@
+package `in`.koreatech.bus.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.feature.bus.R
+
+@Composable
+fun CommonEmptyView(
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier.width(120.dp),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_empty_bus),
+ contentDescription = stringResource(R.string.no_result),
+ )
+ Spacer(modifier = Modifier.height(32.dp))
+ Text(
+ modifier = Modifier,
+ text = stringResource(R.string.empty_description),
+ style = KoinTheme.typography.bold15,
+ fontWeight = FontWeight.SemiBold
+ )
+ Spacer(modifier = Modifier.height(64.dp))
+ }
+}
+
+@Preview
+@Composable
+private fun CommonFailureViewPreview() {
+ CommonFailureView()
+}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/CommonFailureView.kt b/feature/bus/src/main/java/in/koreatech/bus/component/CommonFailureView.kt
new file mode 100644
index 000000000..d2e8bd149
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/CommonFailureView.kt
@@ -0,0 +1,82 @@
+package `in`.koreatech.bus.component
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Refresh
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+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.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.util.LocalOnRefresh
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.feature.bus.R
+
+@Composable
+fun CommonFailureView(
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier.width(120.dp),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_fail_whale),
+ contentDescription = stringResource(R.string.error),
+ )
+ Spacer(modifier = Modifier.height(32.dp))
+ Text(
+ modifier = Modifier,
+ text = stringResource(R.string.fail_to_load_screen_description),
+ style = KoinTheme.typography.bold15,
+ fontWeight = FontWeight.SemiBold
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ Button(
+ onClick = LocalOnRefresh.current,
+ contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.White,
+ contentColor = Color.Black
+ ),
+ border = BorderStroke(1.dp, KoinTheme.colors.neutral300),
+ shape = CircleShape
+ ) {
+ Icon(
+ imageVector = Icons.Rounded.Refresh,
+ contentDescription = stringResource(R.string.refresh),
+ )
+ Text(
+ modifier = Modifier.padding(start = 6.dp),
+ text = stringResource(R.string.refresh),
+ style = KoinTheme.typography.regular14,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun CommonFailureViewPreview() {
+ CommonFailureView()
+}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/CommonLoadingView.kt b/feature/bus/src/main/java/in/koreatech/bus/component/CommonLoadingView.kt
similarity index 97%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/CommonLoadingView.kt
rename to feature/bus/src/main/java/in/koreatech/bus/component/CommonLoadingView.kt
index 01873560b..b10799205 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/CommonLoadingView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/CommonLoadingView.kt
@@ -1,4 +1,4 @@
-package `in`.koreatech.bus.screen
+package `in`.koreatech.bus.component
import androidx.annotation.RawRes
import androidx.compose.foundation.layout.Box
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/NoticeItem.kt b/feature/bus/src/main/java/in/koreatech/bus/component/NoticeItem.kt
new file mode 100644
index 000000000..b97baa53c
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/NoticeItem.kt
@@ -0,0 +1,93 @@
+package `in`.koreatech.bus.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Close
+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.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.mock.busNoticeUiStateMock
+import `in`.koreatech.bus.state.BusNoticeState
+import `in`.koreatech.koin.core.designsystem.noRippleClickable
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.core.designsystem.util.getMeasuredKoreanHeightDp
+
+@Composable
+internal fun NoticeItem(
+ notice: BusNoticeState,
+ onNoticeClick: (BusNoticeState) -> Unit,
+ onCloseIconClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ noticeMaxLines: Int = 1,
+ textStyle: TextStyle = KoinTheme.typography.medium14
+) {
+ val textHeightDp = textStyle.getMeasuredKoreanHeightDp()
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(
+ color = KoinTheme.colors.info100,
+ shape = RoundedCornerShape(8.dp)
+ ).clickable { onNoticeClick(notice) }
+ .padding(16.dp),
+ verticalAlignment = if (noticeMaxLines == 1) Alignment.CenterVertically else Alignment.Top
+ ) {
+ Text(
+ modifier = Modifier.weight(1f),
+ text = notice.title,
+ style = textStyle,
+ color = KoinTheme.colors.primary500,
+ maxLines = noticeMaxLines,
+ overflow = TextOverflow.Ellipsis
+ )
+ Icon(
+ modifier = Modifier.padding(start = 4.dp).padding(vertical = (textHeightDp - 16.dp) / 2).size(16.dp).noRippleClickable {
+ onCloseIconClick()
+ },
+ imageVector = Icons.Rounded.Close,
+ contentDescription = notice.title,
+ tint = KoinTheme.colors.neutral400
+ )
+ }
+}
+
+@Composable
+@Preview
+private fun NoticeItemPreview() {
+ KoinTheme {
+ NoticeItem(
+ notice = busNoticeUiStateMock.notice,
+ onCloseIconClick = {},
+ onNoticeClick = {}
+ )
+ }
+}
+
+@Composable
+@Preview
+private fun NoticeItem2Preview() {
+ KoinTheme {
+ NoticeItem(
+ notice = busNoticeUiStateMock.notice,
+ onCloseIconClick = {},
+ onNoticeClick = {},
+ noticeMaxLines = 2
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/ShuttleBusOperationChip.kt b/feature/bus/src/main/java/in/koreatech/bus/component/ShuttleBusOperationChip.kt
new file mode 100644
index 000000000..b078b4096
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/ShuttleBusOperationChip.kt
@@ -0,0 +1,46 @@
+package `in`.koreatech.bus.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+import `in`.koreatech.koin.core.designsystem.component.chip.ReadOnlyTextChip
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+
+@Composable
+internal fun ShuttleBusOperationChip(
+ operationType: ShuttleBusOperationType,
+ modifier: Modifier = Modifier
+) {
+ ReadOnlyTextChip(
+ modifier = modifier,
+ title = stringResource(operationType.simpleTitleRes),
+ containerColor = when (operationType) {
+ ShuttleBusOperationType.WEEKEND -> Color(0xFF34ADFF)
+ ShuttleBusOperationType.WEEKDAY -> Color(0xFFFFB443)
+ ShuttleBusOperationType.CIRCULATION -> Color(0xFF4ED92C)
+ else -> Color.Transparent
+ },
+ textStyle = KoinTheme.typography.regular12.copy(color = Color.White)
+ )
+}
+
+@Preview
+@Composable
+private fun ShuttleBusOperationChipPreview() {
+ ShuttleBusOperationChip(operationType = ShuttleBusOperationType.WEEKEND)
+}
+
+@Preview
+@Composable
+private fun ShuttleBusOperationChipPreview2() {
+ ShuttleBusOperationChip(operationType = ShuttleBusOperationType.WEEKDAY)
+}
+
+@Preview
+@Composable
+private fun ShuttleBusOperationChipPreview3() {
+ ShuttleBusOperationChip(operationType = ShuttleBusOperationType.CIRCULATION)
+}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/component/WrongInformationText.kt b/feature/bus/src/main/java/in/koreatech/bus/component/WrongInformationText.kt
new file mode 100644
index 000000000..cacc25486
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/component/WrongInformationText.kt
@@ -0,0 +1,45 @@
+package `in`.koreatech.bus.component
+
+import android.content.Intent
+import android.net.Uri
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.bus.util.LocalSelectedTimetableTab
+import `in`.koreatech.koin.core.analytics.EventLogger
+import `in`.koreatech.koin.core.designsystem.component.text.LeadingIconText
+import `in`.koreatech.koin.core.designsystem.noRippleClickable
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.feature.bus.R
+
+@Composable
+internal fun WrongInformationText(
+ modifier: Modifier = Modifier,
+ loggingEventValue: String = ""
+) {
+ val context = LocalContext.current
+
+ LeadingIconText(
+ modifier = modifier.noRippleClickable {
+ EventLogger.logCampusClickEvent(
+ "error_feedback_button",
+ loggingEventValue
+ )
+ val url = GOOGLE_FORM_URL
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ context.startActivity(intent)
+ }.padding(top = 4.dp),
+ text = stringResource(R.string.request_for_incorrect_information),
+ iconRes = R.drawable.ic_caution,
+ iconTint = KoinTheme.colors.neutral500,
+ textStyle = KoinTheme.typography.regular12.copy(
+ color = KoinTheme.colors.neutral500
+ )
+ )
+}
+
+private const val GOOGLE_FORM_URL = "https://docs.google.com/forms/d/1GR4t8IfTOrYY4jxq5YAS7YiCS8QIFtHaWu_kE-SdDKY"
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/mock/BusNoticeMock.kt b/feature/bus/src/main/java/in/koreatech/bus/mock/BusNoticeMock.kt
new file mode 100644
index 000000000..8bdf81ce6
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/mock/BusNoticeMock.kt
@@ -0,0 +1,16 @@
+package `in`.koreatech.bus.mock
+
+import `in`.koreatech.bus.screen.timetable.viewmodel.BusNoticeUiState
+import `in`.koreatech.bus.state.BusNoticeState
+
+internal val busNoticeUiStateMock = BusNoticeUiState.Show(
+ BusNoticeState(
+ id = 17153,
+ title = "[긴급] 9.27(금) 대학등교방향 천안셔틀버스 터미널 미정차 알림(천안역에서 승차바람)"
+ )
+)
+
+internal val busNoticeMock = BusNoticeState(
+ id = 17153,
+ title = "[긴급] 9.27(금) 대학등교방향 천안셔틀버스 터미널 미정차 알림(천안역에서 승차바람)"
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/mock/BusSearchMock.kt b/feature/bus/src/main/java/in/koreatech/bus/mock/BusSearchMock.kt
new file mode 100644
index 000000000..cf27fed20
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/mock/BusSearchMock.kt
@@ -0,0 +1,99 @@
+package `in`.koreatech.bus.mock
+
+import `in`.koreatech.bus.state.BusSearchResultState
+import `in`.koreatech.bus.type.BusType
+import java.time.LocalTime
+
+internal val busSearchResultMock1 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(6, 10)
+)
+internal val busSearchResultMock2 = BusSearchResultState(
+ busType = BusType.SHUTTLE,
+ busName = "400",
+ departureTime = LocalTime.of(6, 30)
+)
+internal val busSearchResultMock3 = BusSearchResultState(
+ busType = BusType.EXPRESS,
+ busName = "400",
+ departureTime = LocalTime.of(6, 50)
+)
+internal val busSearchResultMock4 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(7, 5)
+)
+internal val busSearchResultMock5 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(7, 21)
+)
+internal val busSearchResultMock6 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(7, 37)
+)
+internal val busSearchResultMock7 = BusSearchResultState(
+ busType = BusType.EXPRESS,
+ busName = "400",
+ departureTime = LocalTime.of(7, 53)
+)
+internal val busSearchResultMock8 = BusSearchResultState(
+ busType = BusType.SHUTTLE,
+ busName = "400",
+ departureTime = LocalTime.of(8, 9)
+)
+internal val busSearchResultMock9 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(8, 25)
+)
+internal val busSearchResultMock10 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(8, 41)
+)
+internal val busSearchResultMock11 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(8, 57)
+)
+internal val busSearchResultMock12 = BusSearchResultState(
+ busType = BusType.SHUTTLE,
+ busName = "400",
+ departureTime = LocalTime.of(9, 13)
+)
+internal val busSearchResultMock13 = BusSearchResultState(
+ busType = BusType.EXPRESS,
+ busName = "400",
+ departureTime = LocalTime.of(9, 29)
+)
+internal val busSearchResultMock14 = BusSearchResultState(
+ busType = BusType.CITY,
+ busName = "400",
+ departureTime = LocalTime.of(9, 45)
+)
+internal val busSearchResultMock15 = BusSearchResultState(
+ busType = BusType.SHUTTLE,
+ busName = "400",
+ departureTime = LocalTime.of(10, 1)
+)
+
+internal val busSearchResultsMock = listOf(
+ busSearchResultMock1,
+ busSearchResultMock2,
+ busSearchResultMock3,
+ busSearchResultMock4,
+ busSearchResultMock5,
+ busSearchResultMock6,
+ busSearchResultMock7,
+ busSearchResultMock8,
+ busSearchResultMock9,
+ busSearchResultMock10,
+ busSearchResultMock11,
+ busSearchResultMock12,
+ busSearchResultMock13,
+ busSearchResultMock14,
+ busSearchResultMock15
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/mock/CommonTimetableMock.kt b/feature/bus/src/main/java/in/koreatech/bus/mock/CommonTimetableMock.kt
new file mode 100644
index 000000000..6ba01ea0d
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/mock/CommonTimetableMock.kt
@@ -0,0 +1,41 @@
+package `in`.koreatech.bus.mock
+
+import `in`.koreatech.bus.state.CityBusInfoState
+import `in`.koreatech.bus.state.CityTimetableState
+import `in`.koreatech.bus.state.DepartureState
+import `in`.koreatech.bus.state.CommonTimetableState
+import `in`.koreatech.bus.state.ExpressTimetableState
+import java.time.LocalDateTime
+
+internal val commonTimetableMock = CommonTimetableState(
+ amDepartures = listOf(
+ DepartureState("09:00"),
+ DepartureState("09:30"),
+ DepartureState("10:00"),
+ DepartureState("10:30"),
+ ),
+ pmDepartures = listOf(
+ DepartureState("14:30"),
+ DepartureState("21:00"),
+ DepartureState("21:30"),
+ DepartureState("22:00"),
+ DepartureState("22:30"),
+ DepartureState("23:00"),
+ DepartureState("23:30"),
+ ),
+)
+
+internal val expressTimetableMock = ExpressTimetableState(
+ timetable = commonTimetableMock,
+ updatedAt = LocalDateTime.parse("2024-11-11T00:00:00"),
+)
+
+internal val cityTimetableMock = CityTimetableState(
+ departureTimes = commonTimetableMock,
+ busInfo = CityBusInfoState(
+ number = 0,
+ departNode = "",
+ arriveNode = "",
+ ),
+ updatedAt = LocalDateTime.parse("2024-11-11T00:00:00"),
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleCoursesMock.kt b/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleCoursesMock.kt
new file mode 100644
index 000000000..fe06445a2
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleCoursesMock.kt
@@ -0,0 +1,144 @@
+package `in`.koreatech.bus.mock
+
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+import `in`.koreatech.bus.state.ShuttleCourseRegionState
+import `in`.koreatech.bus.state.ShuttleCourseRouteState
+import `in`.koreatech.bus.state.ShuttleCoursesState
+import `in`.koreatech.bus.state.ShuttleSemesterState
+import java.time.LocalDate
+
+internal val shuttleSemesterMock = ShuttleSemesterState(
+ name = "정규학기",
+ from = LocalDate.parse("2024-09-01"),
+ to = LocalDate.parse("2024-12-21")
+)
+
+internal val shuttleCourseRouteMock1 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.CIRCULATION,
+ routeName = "천안셔틀",
+ subName = ""
+)
+internal val shuttleCourseRouteMock2 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "천안역",
+ subName = ""
+)
+internal val shuttleCourseRouteMock3 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "터미널",
+ subName = ""
+)
+internal val shuttleCourseRouteMock4 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "두정역",
+ subName = ""
+)
+internal val shuttleCourseRouteMock5 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "아산 / KTX",
+ subName = ""
+)
+internal val shuttleCourseRouteMock6 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKEND,
+ routeName = "천안셔틀",
+ subName = "토요일, 일요일"
+)
+internal val shuttleCourseRouteMock7 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKEND,
+ routeName = "일학습병행대학 시내",
+ subName = "토요일"
+)
+internal val shuttleCourseRouteMock8 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKEND,
+ routeName = "일학습병행대학 천안아산역",
+ subName = "토요일"
+)
+internal val shuttleCourseRouteMock9 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.CIRCULATION,
+ routeName = "청주셔틀",
+ subName = ""
+)
+internal val shuttleCourseRouteMock10 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "용암동",
+ subName = ""
+)
+internal val shuttleCourseRouteMock11 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "동남지구",
+ subName = ""
+)
+internal val shuttleCourseRouteMock12 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "산남/분평",
+ subName = ""
+)
+internal val shuttleCourseRouteMock13 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "서울 교대역",
+ subName = ""
+)
+internal val shuttleCourseRouteMock14 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "서울 교대역",
+ subName = "월요일"
+)
+internal val shuttleCourseRouteMock15 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "서울 동천역",
+ subName = "월요일"
+)
+internal val shuttleCourseRouteMock16 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "서울 하교 1",
+ subName = "금요일"
+)
+internal val shuttleCourseRouteMock17 = ShuttleCourseRouteState(
+ id = "1",
+ type = ShuttleBusOperationType.WEEKDAY,
+ routeName = "서울 하교 2",
+ subName = "금요일"
+)
+
+internal val shuttleCoursesMock = ShuttleCoursesState(
+ courses = mapOf(
+ ShuttleCourseRegionState("천안・아산") to listOf(
+ shuttleCourseRouteMock1,
+ shuttleCourseRouteMock2,
+ shuttleCourseRouteMock3,
+ shuttleCourseRouteMock4,
+ shuttleCourseRouteMock5,
+ shuttleCourseRouteMock6,
+ shuttleCourseRouteMock7,
+ shuttleCourseRouteMock8
+ ), ShuttleCourseRegionState("청주") to listOf(
+ shuttleCourseRouteMock9,
+ shuttleCourseRouteMock10,
+ shuttleCourseRouteMock11,
+ shuttleCourseRouteMock12
+ ), ShuttleCourseRegionState("서울") to listOf(
+ shuttleCourseRouteMock13,
+ shuttleCourseRouteMock14,
+ shuttleCourseRouteMock15,
+ shuttleCourseRouteMock16,
+ shuttleCourseRouteMock17
+ )
+ ),
+ semester = shuttleSemesterMock
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleTimetableMock.kt b/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleTimetableMock.kt
new file mode 100644
index 000000000..f6395e762
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/mock/ShuttleTimetableMock.kt
@@ -0,0 +1,143 @@
+package `in`.koreatech.bus.mock
+
+import `in`.koreatech.bus.screen.shuttle_timetable.viewmodel.ShuttleTimetableUiState
+import `in`.koreatech.bus.state.ShuttleTimetableNodeInfoState
+import `in`.koreatech.bus.state.ShuttleTimetableRouteInfoState
+import `in`.koreatech.bus.state.ShuttleTimetableState
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+
+internal val shuttleTimetableNodeInfoMock1 = ShuttleTimetableNodeInfoState(
+ name = "본교",
+ detail = ""
+)
+internal val shuttleTimetableNodeInfoMock2 = ShuttleTimetableNodeInfoState(
+ name = "2캠퍼스",
+ detail = ""
+)
+internal val shuttleTimetableNodeInfoMock3 = ShuttleTimetableNodeInfoState(
+ name = "천안터미널",
+ detail = ""
+)
+internal val shuttleTimetableNodeInfoMock4 = ShuttleTimetableNodeInfoState(
+ name = "천안역",
+ detail = ""
+)
+internal val shuttleTimetableNodeInfoMock5 = ShuttleTimetableNodeInfoState(
+ name = "본교",
+ detail = "인문경영관"
+)
+
+internal val shuttleTimetableRouteInfoMock1 = ShuttleTimetableRouteInfoState(
+ name = "1회",
+ arrivalTimes = listOf(
+ "11:10",
+ "",
+ "11:35",
+ "11:40",
+ "12:10"
+ )
+)
+internal val shuttleTimetableRouteInfoMock2 = ShuttleTimetableRouteInfoState(
+ name = "2회",
+ arrivalTimes = listOf(
+ "13:10",
+ "",
+ "13:35",
+ "13:40",
+ "14:10"
+ )
+)
+internal val shuttleTimetableRouteInfoMock3 = ShuttleTimetableRouteInfoState(
+ name = "3회",
+ arrivalTimes = listOf(
+ "14:10",
+ "14:35",
+ "14:42",
+ "14:47",
+ "15:15"
+ )
+)
+internal val shuttleTimetableRouteInfoMock4 = ShuttleTimetableRouteInfoState(
+ name = "4회",
+ arrivalTimes = listOf(
+ "16:10",
+ "",
+ "16:35",
+ "16:40",
+ "17:10"
+ )
+)
+internal val shuttleTimetableRouteInfoMock5 = ShuttleTimetableRouteInfoState(
+ name = "5회",
+ arrivalTimes = listOf(
+ "20:00",
+ "",
+ "20:25",
+ "20:30",
+ "21:00"
+ )
+)
+internal val shuttleTimetableRouteInfoMock6 = ShuttleTimetableRouteInfoState(
+ name = "6회",
+ arrivalTimes = listOf(
+ "21:00",
+ "",
+ "21:25",
+ "21:30",
+ "22:00"
+ )
+)
+internal val shuttleTimetableRouteInfoMock7 = ShuttleTimetableRouteInfoState(
+ name = "목, 금 추가",
+ arrivalTimes = listOf(
+ "14:10/16:30",
+ "",
+ "14:35/16:55",
+ "14:40/17:00",
+ "15:10/17:30"
+ )
+)
+internal val shuttleTimetableUiStateMock1 = ShuttleTimetableUiState.Success(
+ ShuttleTimetableState(
+ region = "천안",
+ routeType = ShuttleBusOperationType.CIRCULATION,
+ routeName = "천안 셔틀",
+ subTitle = "토요일",
+ nodeInfo = listOf(
+ shuttleTimetableNodeInfoMock1,
+ shuttleTimetableNodeInfoMock2,
+ shuttleTimetableNodeInfoMock3,
+ shuttleTimetableNodeInfoMock4,
+ shuttleTimetableNodeInfoMock5
+ ),
+ routeInfo = listOf(
+ shuttleTimetableRouteInfoMock1,
+ shuttleTimetableRouteInfoMock2,
+ shuttleTimetableRouteInfoMock3,
+ shuttleTimetableRouteInfoMock4,
+ shuttleTimetableRouteInfoMock5,
+ shuttleTimetableRouteInfoMock6,
+ shuttleTimetableRouteInfoMock7
+ )
+ )
+)
+
+internal val shuttleTimetableUiStateMock2 = ShuttleTimetableUiState.Success(
+ ShuttleTimetableState(
+ region = "천안",
+ routeType = ShuttleBusOperationType.CIRCULATION,
+ routeName = "대학원",
+ subTitle = "",
+ nodeInfo = listOf(
+ shuttleTimetableNodeInfoMock1,
+ shuttleTimetableNodeInfoMock2,
+ shuttleTimetableNodeInfoMock3,
+ shuttleTimetableNodeInfoMock4,
+ shuttleTimetableNodeInfoMock5
+ ),
+ routeInfo = listOf(
+ shuttleTimetableRouteInfoMock1,
+ shuttleTimetableRouteInfoMock2
+ )
+ )
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/navigation/BusSearchNavigation.kt b/feature/bus/src/main/java/in/koreatech/bus/navigation/BusSearchNavigation.kt
index 23f977257..31d8c708f 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/navigation/BusSearchNavigation.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/navigation/BusSearchNavigation.kt
@@ -1,16 +1,26 @@
package `in`.koreatech.bus.navigation
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import `in`.koreatech.bus.screen.searchresult.composable.BusSearchResultScreen
+import `in`.koreatech.bus.animation.defaultEnterTransition
+import `in`.koreatech.bus.animation.defaultExitTransition
+import `in`.koreatech.bus.animation.defaultOutsideColor
+import `in`.koreatech.bus.animation.defaultPopEnterTransition
+import `in`.koreatech.bus.animation.defaultPopExitTransition
import `in`.koreatech.bus.screen.search.composable.BusSearchScreen
+import `in`.koreatech.bus.screen.searchresult.composable.BusSearchResultScreen
+import `in`.koreatech.bus.type.PlaceType
+import `in`.koreatech.bus.util.findActivity
+import `in`.koreatech.koin.core.analytics.EventLogger
+import kotlin.reflect.typeOf
@Composable
fun BusSearchNavigation(
@@ -18,21 +28,32 @@ fun BusSearchNavigation(
navController: NavHostController = rememberNavController(),
) {
+ val context = LocalContext.current
NavHost(
- modifier = modifier,
+ modifier = modifier.background(defaultOutsideColor),
navController = navController,
startDestination = Routes.BusSearch,
enterTransition = {
- EnterTransition.None
+ defaultEnterTransition()
}, exitTransition = {
- ExitTransition.None
+ defaultExitTransition()
+ }, popEnterTransition = {
+ defaultPopEnterTransition()
+ }, popExitTransition = {
+ defaultPopExitTransition()
}
) {
- composable {
+ composable(
+ typeMap = mapOf(
+ typeOf() to PlaceTypeNavType
+ )
+ ) {
BusSearchScreen(
- modifier = Modifier.fillMaxSize(),
- onNavigationIconClick = { navController.popBackStack() },
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White),
+ onNavigationIconClick = { context.findActivity()?.finish() },
onSearch = { departure, arrival ->
navController.navigate(Routes.BusSearchResult(departure, arrival))
}
@@ -41,9 +62,16 @@ fun BusSearchNavigation(
composable {
BusSearchResultScreen(
- modifier = Modifier.fillMaxSize(),
- onNavigationIconClick = { navController.popBackStack() }
-
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White),
+ onNavigationIconClick = {
+ EventLogger.logCampusClickEvent(
+ "search_result_back",
+ "뒤로가기"
+ )
+ navController.popBackStack()
+ }
)
}
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/navigation/BusTimetableNavigation.kt b/feature/bus/src/main/java/in/koreatech/bus/navigation/BusTimetableNavigation.kt
index 451d81dff..69e62c8fc 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/navigation/BusTimetableNavigation.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/navigation/BusTimetableNavigation.kt
@@ -1,15 +1,23 @@
package `in`.koreatech.bus.navigation
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import `in`.koreatech.bus.animation.defaultEnterTransition
+import `in`.koreatech.bus.animation.defaultExitTransition
+import `in`.koreatech.bus.animation.defaultOutsideColor
+import `in`.koreatech.bus.animation.defaultPopEnterTransition
+import `in`.koreatech.bus.animation.defaultPopExitTransition
+import `in`.koreatech.bus.screen.shuttle_timetable.composable.ShuttleTimetableScreen
import `in`.koreatech.bus.screen.timetable.composable.BusTimetableScreen
+import `in`.koreatech.bus.util.findActivity
@Composable
fun BusTimetableNavigation(
@@ -17,21 +25,39 @@ fun BusTimetableNavigation(
navController: NavHostController = rememberNavController(),
) {
+ val context = LocalContext.current
+
NavHost(
- modifier = modifier,
+ modifier = modifier.background(defaultOutsideColor),
navController = navController,
startDestination = Routes.BusTimetable,
enterTransition = {
- EnterTransition.None
+ defaultEnterTransition()
}, exitTransition = {
- ExitTransition.None
+ defaultExitTransition()
+ }, popEnterTransition = {
+ defaultPopEnterTransition()
+ }, popExitTransition = {
+ defaultPopExitTransition()
}
) {
composable {
BusTimetableScreen(
- modifier = Modifier.fillMaxSize(),
- onNavigationIconClick = { navController.popBackStack() }
+ modifier = Modifier.fillMaxSize().background(Color.White),
+ onNavigationIconClick = {
+ context.findActivity()?.finish()
+ },
+ onNavigateToShuttleTimetableScreen = {
+ navController.navigate(Routes.ShuttleTimetable(it.id))
+ }
+ )
+ }
+
+ composable {
+ ShuttleTimetableScreen(
+ modifier = Modifier.fillMaxSize().background(Color.White),
+ onNavigationIconClick = navController::popBackStack
)
}
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/navigation/PlaceTypeNavType.kt b/feature/bus/src/main/java/in/koreatech/bus/navigation/PlaceTypeNavType.kt
new file mode 100644
index 000000000..e645c56ae
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/navigation/PlaceTypeNavType.kt
@@ -0,0 +1,29 @@
+package `in`.koreatech.bus.navigation
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.core.os.BundleCompat
+import androidx.navigation.NavType
+import `in`.koreatech.bus.type.PlaceType
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+internal object PlaceTypeNavType : NavType(
+ isNullableAllowed = true
+) {
+ override fun get(bundle: Bundle, key: String): PlaceType {
+ return BundleCompat.getSerializable(bundle, key, PlaceType::class.java)!!
+ }
+
+ override fun parseValue(value: String): PlaceType {
+ return Json.decodeFromString(value)
+ }
+
+ override fun put(bundle: Bundle, key: String, value: PlaceType) {
+ bundle.putSerializable(key, value)
+ }
+
+ override fun serializeAsValue(value: PlaceType): String {
+ return Uri.encode(Json.encodeToString(value))
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/navigation/Routes.kt b/feature/bus/src/main/java/in/koreatech/bus/navigation/Routes.kt
index 940faaaca..29682eb84 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/navigation/Routes.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/navigation/Routes.kt
@@ -1,11 +1,13 @@
package `in`.koreatech.bus.navigation
+import `in`.koreatech.bus.type.PlaceType
import kotlinx.serialization.Serializable
internal object Routes {
@Serializable data object BusTimetable
+ @Serializable data class ShuttleTimetable(val id: String)
@Serializable data object BusSearch
- @Serializable data class BusSearchResult(val departure: String, val arrival: String)
+ @Serializable data class BusSearchResult(val departure: PlaceType, val arrival: PlaceType)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/MainEntryView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/MainEntryView.kt
index 73efd372d..f25957d20 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/MainEntryView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/MainEntryView.kt
@@ -20,10 +20,12 @@ 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.res.painterResource
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
@@ -46,7 +48,8 @@ fun MainEntryView(
Text(
text = stringResource(R.string.bus),
style = KoinTheme.typography.bold18,
- color = KoinTheme.colors.primary500
+ color = KoinTheme.colors.primary500,
+ fontSize = 15.sp
)
Spacer(modifier = Modifier.weight(1f))
Row(
@@ -64,7 +67,7 @@ fun MainEntryView(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
- painter = painterResource(R.drawable.ic_qr),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_qr),
contentDescription = stringResource(R.string.unibus_shortcut_content_description),
tint = KoinTheme.colors.primary500
)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreen.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreen.kt
index a1451a5b8..f25164277 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreen.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreen.kt
@@ -1,31 +1,47 @@
package `in`.koreatech.bus.screen.search.composable
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import `in`.koreatech.bus.screen.search.viewmodel.BusSearchViewModel
+import `in`.koreatech.bus.type.PlaceType
+import `in`.koreatech.bus.util.LocalOnRefresh
+import `in`.koreatech.bus.util.goToArticle
@Composable
fun BusSearchScreen(
modifier: Modifier = Modifier,
onNavigationIconClick: () -> Unit = {},
- onSearch: (departure: String, arrival: String) -> Unit = { _, _ -> },
+ onSearch: (departure: PlaceType, arrival: PlaceType) -> Unit = { _, _ -> },
viewModel: BusSearchViewModel = hiltViewModel()
) {
val departure by viewModel.departure.collectAsStateWithLifecycle()
val arrival by viewModel.arrival.collectAsStateWithLifecycle()
+ val busNoticeUiState by viewModel.noticeUiState.collectAsStateWithLifecycle()
- BusSearchScreenContent(
- departure = departure,
- arrival = arrival,
- modifier = modifier,
- onNavigationIconClick = onNavigationIconClick,
- onSwapIconClicked = viewModel::swapDepartureAndArrival,
- onSearchClicked = { onSearch(departure, arrival) },
- onDepartureSet = viewModel::setDeparture,
- onArrivalSet = viewModel::setArrival
- )
+ val context = LocalContext.current
+
+ CompositionLocalProvider(LocalOnRefresh provides viewModel::refresh) {
+ BusSearchScreenContent(
+ departure = departure,
+ arrival = arrival,
+ busNoticeUiState = busNoticeUiState,
+ modifier = modifier,
+ onNavigationIconClick = onNavigationIconClick,
+ onSwapIconClick = viewModel::swapDepartureAndArrival,
+ onSearchClick = {
+ if (departure != null && arrival != null)
+ onSearch(departure!!, arrival!!)
+ },
+ onDepartureSet = viewModel::setDeparture,
+ onArrivalSet = viewModel::setArrival,
+ onCloseNotice = viewModel::closeNotice,
+ onNoticeClick = { context.goToArticle(it.id) }
+ )
+ }
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreenContent.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreenContent.kt
index 7ce9d2ce2..10ec4850f 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreenContent.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchScreenContent.kt
@@ -17,27 +17,48 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.search.type.PlaceSelectMode
+import `in`.koreatech.bus.component.NoticeItem
+import `in`.koreatech.bus.mock.busNoticeUiStateMock
+import `in`.koreatech.bus.screen.timetable.viewmodel.BusNoticeUiState
+import `in`.koreatech.bus.state.BusNoticeState
+import `in`.koreatech.bus.type.PlaceSelectMode
+import `in`.koreatech.bus.type.PlaceType
+import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.designsystem.component.topbar.KoinTopAppBar
import `in`.koreatech.koin.feature.bus.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun BusSearchScreenContent(
- departure: String,
- arrival: String,
+ departure: PlaceType?,
+ arrival: PlaceType?,
+ busNoticeUiState: BusNoticeUiState,
modifier: Modifier = Modifier,
onNavigationIconClick: () -> Unit = {},
- onSwapIconClicked: () -> Unit = {},
- onSearchClicked: () -> Unit = {},
- onDepartureSet: (String) -> Unit = {},
- onArrivalSet: (String) -> Unit = {},
+ onSwapIconClick: () -> Unit = {},
+ onSearchClick: () -> Unit = {},
+ onDepartureSet: (PlaceType) -> Unit = {},
+ onArrivalSet: (PlaceType) -> Unit = {},
+ onCloseNotice: () -> Unit = {},
+ onNoticeClick: (BusNoticeState) -> Unit = {}
) {
+
val context = LocalContext.current
- val searchButtonEnabled by remember(departure, arrival) { derivedStateOf { departure.isNotEmpty() && arrival.isNotEmpty() } }
+ val searchButtonEnabled by remember(
+ departure,
+ arrival
+ ) { derivedStateOf { departure != null && arrival != null } }
var placeSelectMode by rememberSaveable { mutableStateOf(PlaceSelectMode.NONE) }
+ val disabledArrival by remember(departure) {
+ mutableStateOf(departure)
+ }
+
+ val disabledDeparture by remember(arrival) {
+ mutableStateOf(arrival)
+ }
+
Column(
modifier = modifier
) {
@@ -46,18 +67,64 @@ internal fun BusSearchScreenContent(
onNavigationIconClick = onNavigationIconClick
)
+ if (busNoticeUiState is BusNoticeUiState.Show) {
+ NoticeItem(
+ modifier = Modifier.padding(horizontal = 24.dp),
+ notice = busNoticeUiState.notice,
+ onCloseIconClick = {
+ onCloseNotice()
+ EventLogger.logCampusClickEvent(
+ "bus_announcement_close",
+ "교통편 조회하기"
+ )
+ },
+ onNoticeClick = {
+ onNoticeClick(busNoticeUiState.notice)
+ EventLogger.logCampusClickEvent(
+ "bus_announcement",
+ "교통편 조회하기"
+ )
+ },
+ noticeMaxLines = 2
+ )
+ }
+
BusSearchView(
modifier = Modifier
.fillMaxSize()
.padding(top = 16.dp)
.padding(horizontal = 24.dp),
- departure = departure,
- arrival = arrival,
+ departure = departure?.titleRes?.let { stringResource(it) } ?: "",
+ arrival = arrival?.titleRes?.let { stringResource(it) } ?: "",
searchButtonEnabled = searchButtonEnabled,
- onSwapIconClicked = onSwapIconClicked,
- onSearchClicked = onSearchClicked,
- onDepartureFieldClicked = { placeSelectMode = PlaceSelectMode.DEPARTURE },
- onArrivalFieldClicked = { placeSelectMode = PlaceSelectMode.ARRIVAL }
+ onSwapIconClicked = {
+ EventLogger.logCampusClickEvent(
+ "swap_destination",
+ "스왑 버튼"
+ )
+ onSwapIconClick()
+ },
+ onSearchClicked = {
+ EventLogger.logCampusClickEvent(
+ "search_bus",
+ "조회하기"
+ )
+ onSearchClick()
+ },
+ onDepartureFieldClicked = {
+ EventLogger.logCampusClickEvent(
+ "departure_box",
+ "출발지 선택"
+ )
+ placeSelectMode = PlaceSelectMode.DEPARTURE
+ },
+ onArrivalFieldClicked = {
+ EventLogger.logCampusClickEvent(
+ "arrival_box",
+ "목적지 선택"
+ )
+ placeSelectMode = PlaceSelectMode.ARRIVAL
+ }
)
}
@@ -67,17 +134,25 @@ internal fun BusSearchScreenContent(
selectMode = placeSelectMode,
onConfirmSelection = {
if (placeSelectMode == PlaceSelectMode.DEPARTURE) {
+ EventLogger.logCampusClickEvent(
+ "departure_location_confirm",
+ context.getString(it.titleRes)
+ )
placeSelectMode =
- if (arrival.isEmpty()) PlaceSelectMode.ARRIVAL else PlaceSelectMode.NONE
- onDepartureSet(context.getString(it.titleRes))
- }
- else if(placeSelectMode == PlaceSelectMode.ARRIVAL) {
+ if (arrival == null) PlaceSelectMode.ARRIVAL else PlaceSelectMode.NONE
+ onDepartureSet(it)
+ } else if (placeSelectMode == PlaceSelectMode.ARRIVAL) {
+ EventLogger.logCampusClickEvent(
+ "arrival_location_confirm",
+ context.getString(it.titleRes)
+ )
placeSelectMode =
- if (departure.isEmpty()) PlaceSelectMode.DEPARTURE else PlaceSelectMode.NONE
- onArrivalSet(context.getString(it.titleRes))
+ if (departure == null) PlaceSelectMode.DEPARTURE else PlaceSelectMode.NONE
+ onArrivalSet(it)
}
},
modifier = Modifier,
+ disabledPlace = if (placeSelectMode == PlaceSelectMode.DEPARTURE) disabledDeparture else disabledArrival
)
}
}
@@ -87,9 +162,10 @@ internal fun BusSearchScreenContent(
@Composable
private fun BusSearchScreenPreview() {
BusSearchScreenContent(
- departure = "",
- arrival = "",
- modifier = Modifier.fillMaxWidth()
+ departure = null,
+ arrival = null,
+ modifier = Modifier.fillMaxWidth(),
+ busNoticeUiState = busNoticeUiStateMock
)
}
@@ -97,18 +173,21 @@ private fun BusSearchScreenPreview() {
@Composable
private fun BusSearchScreen2Preview() {
BusSearchScreenContent(
- departure = "코리아텍",
- arrival = "",
- modifier = Modifier.fillMaxWidth()
+ departure = PlaceType.KOREATECH,
+ arrival = null,
+ modifier = Modifier.fillMaxWidth(),
+ busNoticeUiState = busNoticeUiStateMock
)
}
+
@Preview(showBackground = true)
@Composable
private fun BusSearchScreen3Preview() {
BusSearchScreenContent(
- departure = "",
- arrival = "천안역",
- modifier = Modifier.fillMaxWidth()
+ departure = null,
+ arrival = PlaceType.STATION,
+ modifier = Modifier.fillMaxWidth(),
+ busNoticeUiState = BusNoticeUiState.NotShow
)
}
@@ -116,8 +195,9 @@ private fun BusSearchScreen3Preview() {
@Composable
private fun BusSearchScreen4Preview() {
BusSearchScreenContent(
- departure = "코리아텍",
- arrival = "천안역",
- modifier = Modifier.fillMaxWidth()
+ departure = PlaceType.KOREATECH,
+ arrival = PlaceType.STATION,
+ modifier = Modifier.fillMaxWidth(),
+ busNoticeUiState = busNoticeUiStateMock
)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchView.kt
index 47281cd80..3e4d99f4a 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/BusSearchView.kt
@@ -22,8 +22,9 @@ 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.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
@@ -128,7 +129,7 @@ internal fun BusSearchView(
).size(32.dp)
) {
Icon(
- painter = painterResource(id = R.drawable.ic_swap),
+ imageVector = ImageVector.vectorResource(id = R.drawable.ic_swap),
contentDescription = stringResource(R.string.swap_content_description),
tint = KoinTheme.colors.primary500
)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/SelectPlaceBottomSheet.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/SelectPlaceBottomSheet.kt
index cdef4448c..7ddc2f748 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/SelectPlaceBottomSheet.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/search/composable/SelectPlaceBottomSheet.kt
@@ -1,5 +1,8 @@
package `in`.koreatech.bus.screen.search.composable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -17,33 +20,36 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.search.type.PlaceSelectMode
-import `in`.koreatech.bus.screen.search.type.PlaceType
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.type.PlaceSelectMode
+import `in`.koreatech.bus.type.PlaceType
import `in`.koreatech.koin.core.designsystem.component.button.FilledButton
-import `in`.koreatech.koin.core.designsystem.component.chip.TextChipGroup
+import `in`.koreatech.koin.core.designsystem.component.chip.TextChip
+import `in`.koreatech.koin.core.designsystem.component.chip.TextChipDefaults
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
internal fun SelectPlaceBottomSheet(
onDismissRequest: () -> Unit,
selectMode: PlaceSelectMode,
onConfirmSelection: (selectedPlace: PlaceType) -> Unit,
modifier: Modifier = Modifier,
+ disabledPlace: PlaceType? = null,
) {
require(selectMode != PlaceSelectMode.NONE) {
"SelectPlaceBottomSheet should not be used with PlaceSelectMode.NONE"
}
- val context = LocalContext.current
+ var selectedPlace by remember(disabledPlace) { mutableStateOf(PlaceType.entries.first {
+ it != disabledPlace
+ }) }
- var selectedPlace by remember { mutableStateOf(PlaceType.KOREATECH) }
val sheetTitle = when (selectMode) {
PlaceSelectMode.DEPARTURE -> stringResource(R.string.question_departure)
PlaceSelectMode.ARRIVAL -> stringResource(R.string.question_arrival)
@@ -66,30 +72,48 @@ internal fun SelectPlaceBottomSheet(
text = sheetTitle,
style = KoinTheme.typography.medium18,
fontWeight = FontWeight.SemiBold,
- modifier = Modifier.padding(horizontal = 32.dp).padding(bottom = 12.dp)
+ modifier = Modifier
+ .padding(horizontal = 32.dp)
+ .padding(bottom = 12.dp)
)
- HorizontalDivider(
- color = KoinTheme.colors.neutral200
- )
+ HorizontalDivider(color = KoinTheme.colors.neutral200)
- TextChipGroup(
+ FlowRow(
modifier = Modifier.padding(horizontal = 32.dp, vertical = 16.dp),
- titles = PlaceType.entries.map { context.getString(it.titleRes) },
- onChipSelected = {
- selectedPlace = PlaceType.entries.find { type ->
- context.getString(type.titleRes) == it
- } ?: PlaceType.KOREATECH
- },
- selectedChipIndexes = intArrayOf(selectedPlace.ordinal),
- shape = RoundedCornerShape(4.dp),
- contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
- showClickRipple = false
- )
+ horizontalArrangement = Arrangement.spacedBy(14.dp)
+ ) {
+ PlaceType.entries.fastForEach {
+ TextChip(
+ shape = RoundedCornerShape(4.dp),
+ showClickRipple = false,
+ title = stringResource(it.titleRes),
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
+ chipColors = if (disabledPlace == it) TextChipDefaults.chipColors(
+ unselectedContainerColor = KoinTheme.colors.neutral50,
+ unselectedContentColor = KoinTheme.colors.neutral300
+ ) else TextChipDefaults.chipColors(
+ unselectedContainerColor = KoinTheme.colors.neutral200,
+ unselectedContentColor = KoinTheme.colors.neutral600
+ ),
+ isSelected = selectedPlace == it,
+ onSelect = {
+ if (it != disabledPlace) {
+ selectedPlace = PlaceType.entries.find { type ->
+ type == it
+ } ?: PlaceType.KOREATECH
+ }
+ }
+ )
+ }
+ }
Spacer(modifier = Modifier.height(140.dp))
FilledButton(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 32.dp).padding(bottom = 36.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 32.dp)
+ .padding(bottom = 36.dp),
text = buttonText,
onClick = { onConfirmSelection(selectedPlace) },
contentPadding = PaddingValues(vertical = 12.dp)
@@ -103,6 +127,7 @@ private fun SelectPlaceBottomSheetPreview() {
SelectPlaceBottomSheet(
onDismissRequest = {},
selectMode = PlaceSelectMode.DEPARTURE,
- onConfirmSelection = {}
+ onConfirmSelection = {},
+ disabledPlace = PlaceType.TERMINAL
)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceType.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceType.kt
deleted file mode 100644
index 6a3c2335e..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceType.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package `in`.koreatech.bus.screen.search.type
-
-import androidx.annotation.StringRes
-import `in`.koreatech.koin.feature.bus.R
-
-internal enum class PlaceType(
- @StringRes val titleRes: Int
-) {
- KOREATECH(R.string.koreatech),
- CHEONAN_STATION(R.string.cheonan_station),
- CHEONAN_TERMINAL(R.string.cheonan_terminal),
-}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/viewmodel/BusSearchViewModel.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/search/viewmodel/BusSearchViewModel.kt
index 29684063f..fc9676023 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/viewmodel/BusSearchViewModel.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/search/viewmodel/BusSearchViewModel.kt
@@ -1,23 +1,81 @@
package `in`.koreatech.bus.screen.search.viewmodel
import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
+import `in`.koreatech.bus.BaseBusViewModel
+import `in`.koreatech.bus.mock.busNoticeMock
+import `in`.koreatech.bus.screen.timetable.viewmodel.BusNoticeUiState
+import `in`.koreatech.bus.state.toBusNoticeState
+import `in`.koreatech.bus.type.PlaceType
+import `in`.koreatech.koin.core.onboarding.OnboardingManager
+import `in`.koreatech.koin.core.onboarding.OnboardingType
+import `in`.koreatech.koin.domain.repository.BusRepository
+import `in`.koreatech.koin.feature.bus.BuildConfig
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class BusSearchViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
-) : ViewModel() {
+ private val onboardingManager: OnboardingManager,
+ private val busRepository: BusRepository
+) : BaseBusViewModel() {
- val departure = savedStateHandle.getStateFlow(KEY_DEPARTURE, "")
- val arrival = savedStateHandle.getStateFlow(KEY_ARRIVAL, "")
+ private val shouldShowNotice = onboardingManager.getShouldOnboardFlow(
+ OnboardingType.SHOW_BUS_HEAD_ARTICLE
+ )
- fun setDeparture(departure: String) {
+ private val notice = flow {
+ busRepository.fetchBusNotice().onSuccess {
+ emit(it.toBusNoticeState())
+ }.onFailure {
+ if (BuildConfig.DEBUG) emit(busNoticeMock)
+ else emit(null)
+ }
+ }
+
+ val noticeUiState = combine(notice, shouldShowNotice) { notice, shouldShow ->
+ if (notice == null)
+ BusNoticeUiState.LoadFailed
+ else {
+ val lastNoticeId = busRepository.getLastShownNoticeId().getOrElse { -1 }
+
+ busRepository.saveLastShownNoticeId(notice.id).getOrNull() ?: return@combine BusNoticeUiState.LoadFailed
+ if (notice.id == lastNoticeId) {
+ if (shouldShow)
+ BusNoticeUiState.Show(notice)
+ else
+ BusNoticeUiState.NotShow
+ } else {
+ onboardingManager.updateShouldOnboard(OnboardingType.SHOW_BUS_HEAD_ARTICLE, true)
+ BusNoticeUiState.Show(notice)
+ }
+ }
+ }.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = BusNoticeUiState.Loading
+ )
+
+ fun closeNotice() {
+ viewModelScope.launch {
+ onboardingManager.updateShouldOnboard(OnboardingType.SHOW_BUS_HEAD_ARTICLE, false)
+ }
+ }
+
+ val departure = savedStateHandle.getStateFlow(KEY_DEPARTURE, null)
+ val arrival = savedStateHandle.getStateFlow(KEY_ARRIVAL, null)
+
+ fun setDeparture(departure: PlaceType?) {
savedStateHandle[KEY_DEPARTURE] = departure
}
- fun setArrival(arrival: String) {
+ fun setArrival(arrival: PlaceType?) {
savedStateHandle[KEY_ARRIVAL] = arrival
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultItem.kt
index 98a5a8d48..f9136e46b 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultItem.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultItem.kt
@@ -9,14 +9,24 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.viewstate.BusDepartureInfoViewState
+import `in`.koreatech.bus.component.BusTypeChip
+import `in`.koreatech.bus.mock.busSearchResultMock1
+import `in`.koreatech.bus.state.BusSearchResultState
+import `in`.koreatech.bus.state.ImmutableLocalTime
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.bus.util.formatBeforeTime
+import `in`.koreatech.bus.util.formatTime
import `in`.koreatech.koin.core.designsystem.component.chip.ReadOnlyTextChip
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import java.time.LocalTime
@Composable
fun BusSearchResultItem(
- info: BusDepartureInfoViewState,
+ result: BusSearchResultState,
+ currentTime: ImmutableLocalTime,
+ showBeforeTime: Boolean,
modifier: Modifier = Modifier,
) {
Row(
@@ -26,24 +36,38 @@ fun BusSearchResultItem(
Column(
modifier = Modifier.weight(1f)
) {
- ReadOnlyTextChip(
- title = stringResource(info.type.titleRes), // TODO : 버스 종류
- containerColor = Color(0xFFFBEBD7),
- textStyle = KoinTheme.typography.regular12.copy(
- color = KoinTheme.colors.neutral600
- )
- )
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ BusTypeChip(busType = result.busType)
+ if (result.busType == BusType.CITY)
+ Text(
+ modifier = Modifier.padding(start = 6.dp),
+ text = result.busName + "번",
+ style = KoinTheme.typography.medium14,
+ color = KoinTheme.colors.neutral800
+ )
+ }
Text(
modifier = Modifier.padding(top = 4.dp),
- text = info.departureHour.toString() + ":" + info.departureMinute.toString().padStart(2, '0'), // TODO : 출발 시간
+ text = result.departureTime.formatTime(),
style = KoinTheme.typography.bold20
)
}
- Text(
- text = "${info.remainingTime}분 전", // TODO : 남은 시간
- style = KoinTheme.typography.bold16.copy(
- color = KoinTheme.colors.info700
+ if (showBeforeTime)
+ Text(
+ text = result.departureTime.formatBeforeTime(currentTime.localTime),
+ style = KoinTheme.typography.bold16.copy(
+ color = KoinTheme.colors.info700
+ )
)
- )
}
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun BusSearchResultItemPreview() {
+ BusSearchResultItem(
+ result = busSearchResultMock1,
+ currentTime = ImmutableLocalTime(LocalTime.now()),
+ showBeforeTime = true
+ )
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreen.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreen.kt
index 3bf25e3c7..7d7aa77ea 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreen.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreen.kt
@@ -1,11 +1,13 @@
package `in`.koreatech.bus.screen.searchresult.composable
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import `in`.koreatech.bus.screen.searchresult.viewmodel.BusSearchResultViewModel
+import `in`.koreatech.bus.util.LocalOnRefresh
import `in`.koreatech.bus.util.formatDateValue
import kotlinx.collections.immutable.toImmutableList
@@ -21,22 +23,29 @@ fun BusSearchResultScreen(
val selectedDaytimeIndex by viewModel.selectedDaytimeIndex.collectAsStateWithLifecycle()
val selectedHourIndex by viewModel.selectedHourIndex.collectAsStateWithLifecycle()
val selectedMinuteIndex by viewModel.selectedMinuteIndex.collectAsStateWithLifecycle()
+ val currentTime by viewModel.currentTime.collectAsStateWithLifecycle()
+ val selectedBusMenu by viewModel.selectedBusTypeMenu.collectAsStateWithLifecycle()
- BusSearchResultScreenContent(
- modifier = modifier,
- searchResultUiState = searchResultUiState,
- onNavigationIconClick = onNavigationIconClick,
- dateList = viewModel.localDates.map { it.formatDateValue() }.toImmutableList(),
- daytimeList = viewModel.daytimeList.toImmutableList(),
- hourList = viewModel.hourList.toImmutableList(),
- minuteList = viewModel.minuteList.toImmutableList(),
- selectedDateIndex = selectedDateIndex,
- selectedDaytimeIndex = selectedDaytimeIndex,
- selectedHourIndex = selectedHourIndex,
- selectedMinuteIndex = selectedMinuteIndex,
- onMinDepartureTimeSetToNow = viewModel::setDepartureTimeToNow,
- onCompleteMinDepartureTime = viewModel::setDepartureTime,
- departure = viewModel.departure,
- arrival = viewModel.arrival,
- )
+ CompositionLocalProvider(LocalOnRefresh provides viewModel::refresh) {
+ BusSearchResultScreenContent(
+ modifier = modifier,
+ searchResultUiState = searchResultUiState,
+ onNavigationIconClick = onNavigationIconClick,
+ dateList = viewModel.localDates.map { it.formatDateValue() }.toImmutableList(),
+ daytimeList = viewModel.daytimeList.toImmutableList(),
+ hourList = viewModel.hourList.toImmutableList(),
+ minuteList = viewModel.minuteList.toImmutableList(),
+ currentTime = currentTime,
+ selectedDateIndex = selectedDateIndex,
+ selectedDaytimeIndex = selectedDaytimeIndex,
+ selectedHourIndex = selectedHourIndex,
+ selectedMinuteIndex = selectedMinuteIndex,
+ onMinDepartureTimeSetToNow = viewModel::setDepartureTimeToNow,
+ onCompleteMinDepartureTime = viewModel::setDepartureTime,
+ departure = viewModel.departure,
+ arrival = viewModel.arrival,
+ onBusTypeChange = viewModel::onBusTypeMenuChanged,
+ selectedBusType = selectedBusMenu,
+ )
+ }
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreenContent.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreenContent.kt
index 90cbdec14..0f6edc245 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreenContent.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/BusSearchResultScreenContent.kt
@@ -1,21 +1,28 @@
package `in`.koreatech.bus.screen.searchresult.composable
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowDown
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -23,40 +30,52 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.CommonLoadingView
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.component.CommonEmptyView
+import `in`.koreatech.bus.component.CommonFailureView
+import `in`.koreatech.bus.mock.busSearchResultsMock
import `in`.koreatech.bus.screen.search.composable.BusSearchConditionSelectDialog
+import `in`.koreatech.bus.screen.searchresult.composable.loading.BusSearchResultLoadingItem
import `in`.koreatech.bus.screen.searchresult.viewmodel.BusSearchResultUiState
-import `in`.koreatech.bus.screen.timetable.type.BusType
+import `in`.koreatech.bus.state.ImmutableLocalTime
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.bus.type.PlaceType
+import `in`.koreatech.bus.util.formatDateValue
import `in`.koreatech.bus.util.formatDepartureTime
-import `in`.koreatech.bus.viewstate.BusDepartureInfoViewState
+import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.designsystem.component.topbar.KoinTopAppBar
import `in`.koreatech.koin.core.designsystem.noRippleClickable
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-import java.util.Locale
+import java.time.LocalDate
+import java.time.LocalTime
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun BusSearchResultScreenContent(
- departure: String,
- arrival: String,
+ departure: PlaceType,
+ arrival: PlaceType,
searchResultUiState: BusSearchResultUiState,
dateList: ImmutableList,
daytimeList: ImmutableList,
hourList: ImmutableList,
minuteList: ImmutableList,
+ currentTime: ImmutableLocalTime,
modifier: Modifier = Modifier,
selectedDateIndex: Int,
selectedDaytimeIndex: Int,
@@ -65,74 +84,195 @@ internal fun BusSearchResultScreenContent(
onNavigationIconClick: () -> Unit = {},
onMinDepartureTimeSetToNow: () -> Unit = {},
onCompleteMinDepartureTime: (dateIndex: Int, daytimeIndex: Int, hourIndex: Int, minuteIndex: Int) -> Unit = { _, _, _, _ -> },
+ selectedBusType: BusType,
+ onBusTypeChange: (BusType) -> Unit = {}
) {
+ val context = LocalContext.current
var showSelectDialog by rememberSaveable { mutableStateOf(false) }
- val departureTime by remember(selectedDateIndex, selectedDaytimeIndex, selectedHourIndex, selectedMinuteIndex) { mutableStateOf(formatDepartureTime(
- dateList[selectedDateIndex],
- daytimeList[selectedDaytimeIndex],
- hourList[selectedHourIndex],
- minuteList[selectedMinuteIndex]
- )) }
+ val departureTime by remember(
+ selectedDateIndex,
+ selectedDaytimeIndex,
+ selectedHourIndex,
+ selectedMinuteIndex
+ ) {
+ mutableStateOf(
+ formatDepartureTime(
+ dateList[selectedDateIndex],
+ daytimeList[selectedDaytimeIndex],
+ hourList[selectedHourIndex],
+ minuteList[selectedMinuteIndex]
+ )
+ )
+ }
+
+ val initialDepartureTimeTextSize = 16.sp
+ var isDropdownExpanded by remember { mutableStateOf(false) }
+ var departureTimeTextSize by remember { mutableStateOf(initialDepartureTimeTextSize) }
Column(
modifier = modifier
) {
KoinTopAppBar(
- title = stringResource(R.string.search_result_direction_title, departure, arrival),
+ title = stringResource(
+ R.string.search_result_direction_title,
+ stringResource(departure.titleRes),
+ stringResource(arrival.titleRes)
+ ),
onNavigationIconClick = onNavigationIconClick
)
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 16.dp, horizontal = 24.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Row(
- modifier = Modifier.noRippleClickable {
- showSelectDialog = true
- }, verticalAlignment = Alignment.CenterVertically
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp, horizontal = 24.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
) {
- Text(
- text = buildAnnotatedString {
- append(departureTime)
- withStyle(
- style = SpanStyle(
- fontWeight = FontWeight.Medium,
- color = Color.Black
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ ) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
+ Row(
+ modifier = Modifier
+ .clip(RoundedCornerShape(12.dp))
+ .clickable {
+ isDropdownExpanded = !isDropdownExpanded
+ }
+ .background(
+ color = KoinTheme.colors.neutral50,
+ shape = RoundedCornerShape(12.dp)
+ )
+ .padding(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = stringResource(selectedBusType.titleRes) + if (selectedBusType != BusType.ALL) stringResource(
+ R.string.bus
+ ) else "",
+ style = KoinTheme.typography.medium14,
+ color = KoinTheme.colors.neutral800,
+ modifier = Modifier.padding(start = 8.dp),
+ maxLines = 1
+ )
+
+ Icon(
+ modifier = Modifier.padding(start = 4.dp),
+ imageVector = Icons.Rounded.KeyboardArrowDown,
+ contentDescription = stringResource(R.string.select_bus_type_content_description),
)
+ }
+
+ DropdownMenu(
+ modifier = Modifier,
+ expanded = isDropdownExpanded,
+ onDismissRequest = { isDropdownExpanded = false },
+ shape = RoundedCornerShape(12.dp),
+ containerColor = KoinTheme.colors.neutral50,
) {
- append(" " + stringResource(R.string.departure))
+ BusType.entries.fastForEach { busType ->
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = stringResource(busType.titleRes) + if (busType != BusType.ALL) stringResource(
+ R.string.bus
+ ) else "",
+ style = KoinTheme.typography.medium14,
+ )
+ },
+ onClick = {
+ EventLogger.logCampusClickEvent(
+ "search_result_bus_type",
+ context.getString(busType.titleRes)
+ )
+ isDropdownExpanded = false
+ onBusTypeChange(busType)
+ }
+ )
+ }
}
- }, style = KoinTheme.typography.bold16,
- color = KoinTheme.colors.info700
- )
- Icon(
- modifier = Modifier.padding(start = 4.dp),
- imageVector = Icons.Rounded.KeyboardArrowDown,
- contentDescription = stringResource(R.string.set_time_content_description),
- tint = KoinTheme.colors.neutral500
- )
+ }
+ }
+
+ Row(
+ modifier = Modifier.noRippleClickable {
+ EventLogger.logCampusClickEvent(
+ "search_result_departure_time",
+ "출발 시각 설정"
+ )
+ showSelectDialog = true
+ }, verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.padding(start = 4.dp),
+ imageVector = Icons.Rounded.KeyboardArrowDown,
+ contentDescription = stringResource(R.string.set_time_content_description),
+ tint = KoinTheme.colors.neutral500
+ )
+ Text(
+ text = buildAnnotatedString {
+ append(departureTime)
+ withStyle(
+ style = SpanStyle(
+ fontWeight = FontWeight.Medium,
+ color = Color.Black
+ )
+ ) {
+ append(" " + stringResource(R.string.departure))
+ }
+ }, style = KoinTheme.typography.bold16,
+ color = KoinTheme.colors.info700,
+ fontSize = departureTimeTextSize,
+ maxLines = 1,
+ onTextLayout = {
+ if (it.didOverflowWidth)
+ departureTimeTextSize = (departureTimeTextSize.value - .25f).sp
+ }, softWrap = false
+ )
+ }
}
- Spacer(modifier = Modifier.weight(1f))
}
when (searchResultUiState) {
is BusSearchResultUiState.Success -> LazyColumn {
- items(searchResultUiState.departureInfos) { info ->
+ items(searchResultUiState.results) { result ->
BusSearchResultItem(
modifier = Modifier
.fillMaxWidth()
- .padding(horizontal = 32.dp, vertical = 8.dp),
- info = info
+ .padding(horizontal = 32.dp, vertical = 10.dp),
+ result = result,
+ currentTime = currentTime,
+ showBeforeTime = (selectedDateIndex == 0 && result.departureTime.isAfter(
+ currentTime.localTime
+ )),
)
}
+ item {
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(120.dp)
+ )
+ }
+ }
+
+ is BusSearchResultUiState.ResultEmpty -> {
+ CommonEmptyView(modifier = Modifier.fillMaxSize())
}
+
is BusSearchResultUiState.Loading -> {
- CommonLoadingView(modifier = Modifier.fillMaxSize())
+ repeat(25) {
+ BusSearchResultLoadingItem(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 32.dp, vertical = 8.dp)
+ )
+ }
}
+
is BusSearchResultUiState.LoadFailed -> {
- // TODO : 에러 화면, Pull to refresh
+ CommonFailureView(modifier = Modifier.fillMaxSize())
}
}
}
@@ -145,10 +285,23 @@ internal fun BusSearchResultScreenContent(
),
onDismissRequest = { showSelectDialog = false },
onDepartureNow = {
+ EventLogger.logCampusClickEvent(
+ "departure_now",
+ "지금 출발"
+ )
onMinDepartureTimeSetToNow()
showSelectDialog = false
},
onComplete = { date, daytime, hour, minute ->
+ EventLogger.logCampusClickEvent(
+ "departure_time_setting_done",
+ "시간설정: " + if (selectedDateIndex == date
+ && selectedDaytimeIndex == daytime
+ && selectedHourIndex == hour
+ && selectedMinuteIndex == minute
+ )
+ "N" else "Y"
+ )
onCompleteMinDepartureTime(date, daytime, hour, minute)
showSelectDialog = false
}, dateList = dateList,
@@ -168,29 +321,20 @@ internal fun BusSearchResultScreenContent(
private fun BusSearchResultScreenPreview() {
BusSearchResultScreenContent(
modifier = Modifier.fillMaxSize(),
- searchResultUiState = previewSearchResultUiState,
- dateList = buildList {
- val today = LocalDateTime.now()
-
- add("오늘")
- add("내일")
- for (i in 2 until 365) {
- val date = today.plusDays(i.toLong())
- val formattedDate = date.format(
- DateTimeFormatter.ofPattern("M월 d일(E)", Locale.KOREA)
- ).replace("요일", "")
- add(formattedDate)
- }
- }.toImmutableList(),
+ searchResultUiState = BusSearchResultUiState.Success(busSearchResultsMock),
+ dateList = List(365) { LocalDate.now().plusDays(it.toLong()) }.map { it.formatDateValue() }
+ .toImmutableList(),
daytimeList = listOf("오전", "오후").toImmutableList(),
hourList = (1..12).map { it.toString() }.toImmutableList(),
minuteList = (0..59).map { it.toString() }.toImmutableList(),
+ currentTime = ImmutableLocalTime(LocalTime.now()),
selectedDateIndex = 0,
selectedDaytimeIndex = 0,
selectedHourIndex = 0,
selectedMinuteIndex = 0,
- departure = "코리아텍",
- arrival = "청주"
+ departure = PlaceType.KOREATECH,
+ arrival = PlaceType.STATION,
+ selectedBusType = BusType.ALL
)
}
@@ -200,108 +344,18 @@ private fun BusSearchResultScreenLoadingPreview() {
BusSearchResultScreenContent(
modifier = Modifier.fillMaxSize(),
searchResultUiState = BusSearchResultUiState.Loading,
- dateList = buildList {
- val today = LocalDateTime.now()
-
- add("오늘")
- add("내일")
- for (i in 2 until 365) {
- val date = today.plusDays(i.toLong())
- val formattedDate = date.format(
- DateTimeFormatter.ofPattern("M월 d일(E)", Locale.KOREA)
- ).replace("요일", "")
- add(formattedDate)
- }
- }.toImmutableList(),
+ dateList = List(365) { LocalDate.now().plusDays(it.toLong()) }.map { it.formatDateValue() }
+ .toImmutableList(),
daytimeList = listOf("오전", "오후").toImmutableList(),
hourList = (1..12).map { it.toString() }.toImmutableList(),
minuteList = (0..59).map { it.toString() }.toImmutableList(),
+ currentTime = ImmutableLocalTime(LocalTime.now()),
selectedDateIndex = 0,
selectedDaytimeIndex = 0,
selectedHourIndex = 0,
selectedMinuteIndex = 0,
- departure = "교대역",
- arrival = "코리아텍"
- )
-}
-
-private val previewSearchResultUiState = BusSearchResultUiState.Success(listOf(
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 9,
- departureMinute = 0,
- remainingTime = 0
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 9,
- departureMinute = 10,
- remainingTime = 10
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 9,
- departureMinute = 20,
- remainingTime = 20
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 9,
- departureMinute = 30,
- remainingTime = 30
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 9,
- departureMinute = 40,
- remainingTime = 40
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 9,
- departureMinute = 50,
- remainingTime = 50
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 10,
- departureMinute = 0,
- remainingTime = 60
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 10,
- departureMinute = 10,
- remainingTime = 70
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 10,
- departureMinute = 20,
- remainingTime = 80
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 10,
- departureMinute = 30,
- remainingTime = 90
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 10,
- departureMinute = 40,
- remainingTime = 100
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 10,
- departureMinute = 50,
- remainingTime = 110
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 11,
- departureMinute = 0,
- remainingTime = 120
+ departure = PlaceType.TERMINAL,
+ arrival = PlaceType.KOREATECH,
+ selectedBusType = BusType.ALL
)
-))
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/loading/BusSearchResultLoadingItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/loading/BusSearchResultLoadingItem.kt
new file mode 100644
index 000000000..7e3c61225
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/composable/loading/BusSearchResultLoadingItem.kt
@@ -0,0 +1,47 @@
+package `in`.koreatech.bus.screen.searchresult.composable.loading
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.animation.skeleton
+
+@Composable
+internal fun BusSearchResultLoadingItem(
+ modifier: Modifier = Modifier
+) {
+
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ Spacer(
+ modifier = Modifier.width(30.dp)
+ .height(16.dp)
+ .skeleton()
+ )
+ Spacer(
+ modifier = Modifier
+ .padding(top = 4.dp)
+ .width(100.dp)
+ .height(20.dp)
+ .skeleton(),
+ )
+ }
+ Spacer(
+ modifier = Modifier
+ .width(80.dp)
+ .height(30.dp)
+ .skeleton()
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/viewmodel/BusSearchResultViewModel.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/viewmodel/BusSearchResultViewModel.kt
index 5e1746a78..5514f2463 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/viewmodel/BusSearchResultViewModel.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/searchresult/viewmodel/BusSearchResultViewModel.kt
@@ -1,18 +1,29 @@
package `in`.koreatech.bus.screen.searchresult.viewmodel
import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import dagger.hilt.android.lifecycle.HiltViewModel
+import `in`.koreatech.bus.BaseBusViewModel
+import `in`.koreatech.bus.mock.busSearchResultsMock
import `in`.koreatech.bus.navigation.Routes
-import `in`.koreatech.bus.screen.timetable.type.BusType
-import `in`.koreatech.bus.viewstate.BusDepartureInfoViewState
-import `in`.koreatech.koin.domain.usecase.busv2.SearchBusV2UseCase
+import `in`.koreatech.bus.state.BusSearchResultState
+import `in`.koreatech.bus.state.ImmutableLocalTime
+import `in`.koreatech.bus.state.toBusSearchResultState
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.koin.domain.repository.BusRepository
+import `in`.koreatech.koin.feature.bus.BuildConfig
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
import java.time.LocalDate
@@ -22,9 +33,9 @@ import javax.inject.Inject
@HiltViewModel
class BusSearchResultViewModel @Inject constructor(
- savedStateHandle: SavedStateHandle,
- private val searchBusV2UseCase: SearchBusV2UseCase
-) : ViewModel() {
+ private val savedStateHandle: SavedStateHandle,
+ private val busRepository: BusRepository
+) : BaseBusViewModel() {
private val arguments = savedStateHandle.toRoute()
val departure = arguments.departure
@@ -44,32 +55,79 @@ class BusSearchResultViewModel @Inject constructor(
private val _selectedMinuteIndex = MutableStateFlow(LocalDateTime.now().minute)
val selectedMinuteIndex = _selectedMinuteIndex.asStateFlow()
- val searchResultUiState = combine(
+ val determinedDepartureTime = combine(
selectedDateIndex,
selectedDaytimeIndex,
selectedHourIndex,
- selectedMinuteIndex
- ) { dateIndex, daytimeIndex, hourIndex, minuteIndex ->
+ selectedMinuteIndex,
+ refreshToggle
+ ) { dateIndex, daytimeIndex, hourIndex, minuteIndex, _ ->
LocalDateTime.of(
localDates[dateIndex],
LocalTime.of(
- if (daytimeList[daytimeIndex] == "오전") hourList[hourIndex].toInt()
- else hourList[hourIndex].toInt() + 12,
+ if (daytimeList[daytimeIndex] == "오전") (hourList[hourIndex].toInt() + 12) % 12
+ else (hourList[hourIndex].toInt() % 12) + 12,
minuteList[minuteIndex].toInt()
)
)
- }.transform {
- searchBusV2UseCase(departure, arrival).onSuccess {
- emit(BusSearchResultUiState.Success(tempData))
+ }.debounce(30L).stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = LocalDateTime.now()
+ )
+
+ val selectedBusTypeMenu = savedStateHandle.getStateFlow(KEY_SELECTED_BUS_TYPE_MENU, BusType.ALL)
+
+ private val searchResult = combineTransform(
+ determinedDepartureTime, selectedBusTypeMenu, refreshToggle
+ ) { requestLocalDateTime, busType, _ ->
+ busRepository.fetchBusSearchResult(
+ date = requestLocalDateTime.toLocalDate(),
+ time = requestLocalDateTime.toLocalTime(),
+ busType = busType.name,
+ departure = departure.name,
+ arrival = arrival.name
+ ).onSuccess { searchResults ->
+ emit(searchResults.map { searchResult -> searchResult.toBusSearchResultState() })
}.onFailure {
+ emit(
+ if (BuildConfig.DEBUG) busSearchResultsMock
+ else null
+ )
+ }
+ }
+
+ val searchResultUiState = searchResult.transform { searchResultStates ->
+ if (searchResultStates == null)
emit(BusSearchResultUiState.LoadFailed)
+ else if (searchResultStates.isEmpty())
+ emit(BusSearchResultUiState.ResultEmpty)
+ else {
+ emit(BusSearchResultUiState.Success(searchResultStates))
}
+ }.catch {
+ emit(BusSearchResultUiState.LoadFailed)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = BusSearchResultUiState.Loading
)
+ val currentTime = flow {
+ while(true) {
+ emit(LocalTime.now())
+ delay(1000L)
+ }
+ }.distinctUntilChangedBy {
+ it.minute
+ }.map {
+ ImmutableLocalTime(it)
+ }.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = ImmutableLocalTime(LocalTime.now())
+ )
+
fun setDepartureTimeToNow() {
val now = LocalDateTime.now()
setDepartureTime(
@@ -85,96 +143,22 @@ class BusSearchResultViewModel @Inject constructor(
_selectedDaytimeIndex.value = daytimeIndex
_selectedHourIndex.value = hourIndex
_selectedMinuteIndex.value = minuteIndex
+ refresh()
+ }
+
+ fun onBusTypeMenuChanged(busType: BusType) {
+ savedStateHandle[KEY_SELECTED_BUS_TYPE_MENU] = busType
}
companion object {
- private const val TOTAL_DATE_COUNT = 365
+ private const val TOTAL_DATE_COUNT = 120
+ private const val KEY_SELECTED_BUS_TYPE_MENU = "selectedBusTypeMenu"
}
}
sealed interface BusSearchResultUiState {
- data class Success(val departureInfos: List) : BusSearchResultUiState
+ data class Success(val results: List) : BusSearchResultUiState
data object Loading : BusSearchResultUiState
data object LoadFailed : BusSearchResultUiState
+ data object ResultEmpty : BusSearchResultUiState
}
-
-private val tempData = listOf(
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 9,
- departureMinute = 0,
- remainingTime = 0
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 9,
- departureMinute = 10,
- remainingTime = 10
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 9,
- departureMinute = 20,
- remainingTime = 20
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 9,
- departureMinute = 30,
- remainingTime = 30
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 9,
- departureMinute = 40,
- remainingTime = 40
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 9,
- departureMinute = 50,
- remainingTime = 50
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 10,
- departureMinute = 0,
- remainingTime = 60
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 10,
- departureMinute = 10,
- remainingTime = 70
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 10,
- departureMinute = 20,
- remainingTime = 80
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 10,
- departureMinute = 30,
- remainingTime = 90
- ),
- BusDepartureInfoViewState(
- type = BusType.EXPRESS,
- departureHour = 10,
- departureMinute = 40,
- remainingTime = 100
- ),
- BusDepartureInfoViewState(
- type = BusType.CITY,
- departureHour = 10,
- departureMinute = 50,
- remainingTime = 110
- ),
- BusDepartureInfoViewState(
- type = BusType.SHUTTLE,
- departureHour = 11,
- departureMinute = 0,
- remainingTime = 120
- )
-)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/NodeItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/NodeItem.kt
new file mode 100644
index 000000000..7ea384cf3
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/NodeItem.kt
@@ -0,0 +1,50 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+
+@Composable
+internal fun NodeItem(
+ nodeTitle: String,
+ modifier: Modifier = Modifier,
+ nodeDescription: String = "",
+) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = if (nodeDescription.isBlank()) Arrangement.Center else Arrangement.Top
+ ) {
+ Text(
+ text = nodeTitle,
+ style = KoinTheme.typography.medium15
+ )
+ if (nodeDescription.isNotBlank())
+ Text(
+ text = nodeDescription,
+ style = KoinTheme.typography.regular12,
+ color = KoinTheme.colors.neutral500
+ )
+ }
+}
+
+@Composable
+@Preview(showBackground = true)
+private fun NodeItemPreview() {
+ NodeItem(
+ nodeTitle = "천안역",
+ nodeDescription = "학화호두과자 앞"
+ )
+}
+
+@Composable
+@Preview(showBackground = true)
+private fun NodeItemNoDescriptionPreview() {
+ NodeItem(
+ nodeTitle = "천안역",
+ nodeDescription = ""
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableNodeItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableNodeItem.kt
new file mode 100644
index 000000000..72793fe14
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableNodeItem.kt
@@ -0,0 +1,74 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+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.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.mock.shuttleTimetableNodeInfoMock1
+import `in`.koreatech.bus.mock.shuttleTimetableNodeInfoMock2
+import `in`.koreatech.bus.mock.shuttleTimetableNodeInfoMock3
+import `in`.koreatech.bus.mock.shuttleTimetableNodeInfoMock4
+import `in`.koreatech.bus.mock.shuttleTimetableNodeInfoMock5
+import `in`.koreatech.bus.state.ShuttleTimetableNodeInfoState
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.feature.bus.R
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+@Composable
+internal fun ShuttleTimetableNodeItem(
+ nodes: ImmutableList,
+ nodeItemHeightDp: Dp,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier.width(IntrinsicSize.Max)
+ ) {
+ Text(
+ text = stringResource(R.string.node_name),
+ style = KoinTheme.typography.regular14,
+ color = KoinTheme.colors.neutral600,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = KoinTheme.colors.neutral100)
+ .padding(horizontal = 24.dp, vertical = 8.dp)
+ )
+
+ nodes.fastForEach { node ->
+ NodeItem(
+ nodeTitle = node.name,
+ nodeDescription = node.detail,
+ modifier = Modifier
+ .padding(horizontal = 24.dp, vertical = 4.dp)
+ .height(nodeItemHeightDp)
+ )
+ }
+ }
+}
+
+@Composable
+@Preview(showBackground = true)
+private fun ShuttleTimetableNodeItemPreview() {
+ ShuttleTimetableNodeItem(
+ persistentListOf(
+ shuttleTimetableNodeInfoMock1,
+ shuttleTimetableNodeInfoMock2,
+ shuttleTimetableNodeInfoMock3,
+ shuttleTimetableNodeInfoMock4,
+ shuttleTimetableNodeInfoMock5
+
+ ),
+ nodeItemHeightDp = 40.dp
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableRouteItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableRouteItem.kt
new file mode 100644
index 000000000..fc4789e00
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableRouteItem.kt
@@ -0,0 +1,69 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+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.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.mock.shuttleTimetableRouteInfoMock1
+import `in`.koreatech.bus.state.ShuttleTimetableRouteInfoState
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+
+@Composable
+fun ShuttleTimetableRouteItem(
+ route: ShuttleTimetableRouteInfoState,
+ nodeItemHeightDp: Dp,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier.width(IntrinsicSize.Max),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = route.name,
+ style = KoinTheme.typography.regular14,
+ color = KoinTheme.colors.neutral600,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = KoinTheme.colors.neutral100)
+ .padding(vertical = 8.dp)
+ )
+
+ route.arrivalTimes.fastForEach { time ->
+ Box(
+ modifier = Modifier
+ .padding(horizontal = 16.dp, vertical = 4.dp)
+ .height(nodeItemHeightDp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = time,
+ style = KoinTheme.typography.bold16,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ShuttleTimetableItemPreview() {
+ ShuttleTimetableRouteItem(
+ route = shuttleTimetableRouteInfoMock1,
+ nodeItemHeightDp = 40.dp
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreen.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreen.kt
new file mode 100644
index 000000000..0ec7365d7
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreen.kt
@@ -0,0 +1,28 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.hilt.navigation.compose.hiltViewModel
+import `in`.koreatech.bus.screen.shuttle_timetable.viewmodel.ShuttleTimetableViewModel
+import `in`.koreatech.bus.util.LocalOnRefresh
+
+@Composable
+fun ShuttleTimetableScreen(
+ modifier: Modifier = Modifier,
+ viewModel: ShuttleTimetableViewModel = hiltViewModel(),
+ onNavigationIconClick: () -> Unit = {}
+) {
+
+ val timetableUiState by viewModel.timetableUiState.collectAsState()
+
+ CompositionLocalProvider(LocalOnRefresh provides viewModel::refresh) {
+ ShuttleTimetableScreenContent(
+ modifier = modifier,
+ onNavigationIconClick = onNavigationIconClick,
+ timetableUiState = timetableUiState
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreenContent.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreenContent.kt
new file mode 100644
index 000000000..f77ec3530
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/ShuttleTimetableScreenContent.kt
@@ -0,0 +1,291 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+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.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerDefaults
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.component.CommonFailureView
+import `in`.koreatech.bus.component.ShuttleBusOperationChip
+import `in`.koreatech.bus.component.WrongInformationText
+import `in`.koreatech.bus.mock.shuttleTimetableUiStateMock1
+import `in`.koreatech.bus.mock.shuttleTimetableUiStateMock2
+import `in`.koreatech.bus.screen.shuttle_timetable.composable.loading.ShuttleTimetableLoading
+import `in`.koreatech.bus.screen.shuttle_timetable.viewmodel.ShuttleTimetableUiState
+import `in`.koreatech.koin.core.analytics.EventLogger
+import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface
+import `in`.koreatech.koin.core.designsystem.component.tab.KoinTabRow
+import `in`.koreatech.koin.core.designsystem.component.topbar.KoinTopAppBar
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.core.designsystem.util.getMeasuredKoreanHeightDp
+import `in`.koreatech.koin.feature.bus.R
+import kotlinx.collections.immutable.toPersistentList
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ShuttleTimetableScreenContent(
+ timetableUiState: ShuttleTimetableUiState,
+ modifier: Modifier = Modifier,
+ onNavigationIconClick: () -> Unit = {}
+) {
+
+ val nodeItemHeightDp =
+ KoinTheme.typography.medium15.getMeasuredKoreanHeightDp() + KoinTheme.typography.regular12.getMeasuredKoreanHeightDp()
+
+ val pagerState = rememberPagerState { 2 }
+ val scope = rememberCoroutineScope()
+
+ val context = LocalContext.current
+ KoinSurface(
+ modifier = modifier
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ KoinTopAppBar(
+ title = stringResource(R.string.title_bus_timetable),
+ onNavigationIconClick = onNavigationIconClick
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ when (timetableUiState) {
+ is ShuttleTimetableUiState.Success -> {
+ val eventValue = "${stringResource(timetableUiState.timetable.routeType.simpleTitleRes)}_${timetableUiState.timetable.routeName}"
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 24.dp, vertical = 16.dp)
+ ) {
+ ShuttleBusOperationChip(
+ operationType = timetableUiState.timetable.routeType
+ )
+
+ Text(
+ text = stringResource(R.string.timetable, timetableUiState.timetable.routeName),
+ style = KoinTheme.typography.bold20,
+ modifier = Modifier.padding(top = 6.dp)
+ )
+ }
+
+ if (timetableUiState.timetable.showTabs()) {
+ KoinTabRow(
+ titles = listOf(
+ stringResource(R.string.tab_title_going),
+ stringResource(R.string.tab_title_return)
+ ),
+ selectedTabIndex = pagerState.currentPage,
+ onTabSelected = {
+ scope.launch {
+ pagerState.animateScrollToPage(it)
+ }
+ }
+ )
+ HorizontalPager(
+ state = pagerState,
+ modifier = Modifier.weight(1f),
+ verticalAlignment = Alignment.Top,
+ flingBehavior = PagerDefaults.flingBehavior(
+ state = pagerState,
+ snapPositionalThreshold = .1f,
+ )
+ ) { page ->
+ Column(
+ modifier = Modifier.fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(14.dp)
+ .background(color = KoinTheme.colors.neutral100)
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min),
+ ) {
+ ShuttleTimetableNodeItem(
+ nodeItemHeightDp = nodeItemHeightDp,
+ nodes = if (page == 0) timetableUiState.timetable.nodeInfo.toPersistentList()
+ else timetableUiState.timetable.nodeInfo.reversed().toPersistentList()
+ )
+
+ VerticalDivider(
+ modifier = Modifier.fillMaxHeight(),
+ color = KoinTheme.colors.neutral300
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .horizontalScroll(rememberScrollState())
+ ) {
+ ShuttleTimetableRouteItem(
+ route = if (page == 0) timetableUiState.timetable.routeInfo[page]
+ else timetableUiState.timetable.routeInfo[page].copy(
+ arrivalTimes = timetableUiState.timetable.routeInfo[page].arrivalTimes.reversed()
+ ),
+ nodeItemHeightDp = nodeItemHeightDp,
+ )
+
+ /** 시간표가 화면을 못 채울 때, 상단의 회색 배경을 채우기 위함 (프리뷰 참조)
+ * 더 좋은 방법을 모르겠음... */
+ Spacer(
+ modifier = Modifier.weight(1f).height(
+ KoinTheme.typography.regular14.getMeasuredKoreanHeightDp() + 16.dp
+ ).background(KoinTheme.colors.neutral100)
+ )
+ }
+ }
+ WrongInformationText(
+ modifier = Modifier.padding(top = 16.dp, start = 24.dp),
+ loggingEventValue = eventValue
+ )
+ Spacer(modifier = Modifier.height(120.dp))
+ }
+ }
+
+ LaunchedEffect(pagerState.currentPage) {
+ EventLogger.logCampusClickEvent(
+ when(pagerState.currentPage) {
+ 0 -> "go_to_school"
+ 1 -> "go_home"
+ else -> "unknown"
+ },
+ eventValue
+ )
+ }
+
+ } else {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ ) {
+ HorizontalDivider(
+ color = KoinTheme.colors.neutral400
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(14.dp)
+ .background(color = KoinTheme.colors.neutral100)
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min),
+ ) {
+ ShuttleTimetableNodeItem(
+ nodeItemHeightDp = nodeItemHeightDp,
+ nodes = timetableUiState.timetable.nodeInfo.toPersistentList()
+ )
+
+ VerticalDivider(
+ modifier = Modifier.fillMaxHeight(),
+ color = KoinTheme.colors.neutral300
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .horizontalScroll(rememberScrollState())
+ ) {
+ timetableUiState.timetable.routeInfo.fastForEach { route ->
+ ShuttleTimetableRouteItem(
+ route = route,
+ nodeItemHeightDp = nodeItemHeightDp,
+ )
+ }
+
+ /** 시간표가 화면을 못 채울 때, 상단의 회색 배경을 채우기 위함 (프리뷰 참조)
+ * 더 좋은 방법을 모르겠음... */
+ Spacer(
+ modifier = Modifier.weight(1f).height(
+ KoinTheme.typography.regular14.getMeasuredKoreanHeightDp() + 16.dp
+ ).background(KoinTheme.colors.neutral100)
+ )
+ }
+ }
+ WrongInformationText(
+ modifier = Modifier.padding(top = 16.dp, start = 24.dp),
+ loggingEventValue = eventValue
+ )
+ Spacer(modifier = Modifier.height(120.dp))
+ }
+ }
+ }
+
+ is ShuttleTimetableUiState.Loading -> {
+ ShuttleTimetableLoading(
+ modifier = Modifier
+ .padding(horizontal = 24.dp, vertical = 16.dp)
+ )
+ }
+
+ is ShuttleTimetableUiState.LoadFailed -> {
+ CommonFailureView(
+ modifier = Modifier
+ .padding(top = 200.dp)
+ .fillMaxSize()
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ShuttleTimetableScreenContentPreview() {
+ ShuttleTimetableScreenContent(
+ timetableUiState = shuttleTimetableUiStateMock1
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ShuttleTimetableScreenContent2Preview() {
+ ShuttleTimetableScreenContent(
+ timetableUiState = shuttleTimetableUiStateMock2
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ShuttleTimetableScreenContentLoadingPreview() {
+ ShuttleTimetableScreenContent(
+ modifier = Modifier.fillMaxSize(),
+ timetableUiState = ShuttleTimetableUiState.Loading
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/loading/ShuttleTimetableLoading.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/loading/ShuttleTimetableLoading.kt
new file mode 100644
index 000000000..18d552ff0
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/composable/loading/ShuttleTimetableLoading.kt
@@ -0,0 +1,89 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.composable.loading
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.animation.skeleton
+
+@Composable
+internal fun ShuttleTimetableLoading(
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier
+ ) {
+ Column(
+ modifier = Modifier
+ ) {
+ Spacer(
+ modifier = Modifier
+ .width(30.dp)
+ .height(16.dp)
+ .skeleton()
+ )
+
+ Spacer(
+ modifier = Modifier
+ .padding(top = 6.dp)
+ .width(120.dp)
+ .height(24.dp)
+ .skeleton(),
+ )
+ }
+
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(24.dp)
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min),
+ ) {
+
+ Column(
+ modifier = Modifier.padding(end = 8.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ repeat(25) {
+ Spacer(
+ modifier = Modifier
+ .width(90.dp)
+ .height(30.dp)
+ .skeleton()
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ repeat(25) {
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(30.dp)
+ .skeleton()
+ )
+ }
+ }
+ }
+
+ Spacer(
+ modifier = Modifier.fillMaxWidth()
+ .height(120.dp)
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/viewmodel/ShuttleTimetableViewModel.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/viewmodel/ShuttleTimetableViewModel.kt
new file mode 100644
index 000000000..0ebe6e2b6
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/shuttle_timetable/viewmodel/ShuttleTimetableViewModel.kt
@@ -0,0 +1,46 @@
+package `in`.koreatech.bus.screen.shuttle_timetable.viewmodel
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.toRoute
+import dagger.hilt.android.lifecycle.HiltViewModel
+import `in`.koreatech.bus.BaseBusViewModel
+import `in`.koreatech.bus.navigation.Routes
+import `in`.koreatech.bus.state.ShuttleTimetableState
+import `in`.koreatech.bus.state.toShuttleTimetableState
+import `in`.koreatech.koin.domain.repository.BusRepository
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+import javax.inject.Inject
+
+@HiltViewModel
+class ShuttleTimetableViewModel @Inject constructor(
+ private val savedStateHandle: SavedStateHandle,
+ private val busRepository: BusRepository
+) : BaseBusViewModel() {
+
+ private val arguments = savedStateHandle.toRoute()
+
+ val timetableUiState = refreshToggle.transform {
+ emit(ShuttleTimetableUiState.Loading)
+ busRepository.fetchShuttleTimetable(arguments.id).onSuccess {
+ emit(ShuttleTimetableUiState.Success(it.toShuttleTimetableState()))
+ }.onFailure {
+ emit(ShuttleTimetableUiState.LoadFailed)
+ }
+ }.catch {
+ emit(ShuttleTimetableUiState.LoadFailed)
+ }.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = ShuttleTimetableUiState.Loading
+ )
+}
+
+sealed interface ShuttleTimetableUiState {
+ data class Success(val timetable: ShuttleTimetableState) : ShuttleTimetableUiState
+ data object Loading : ShuttleTimetableUiState
+ data object LoadFailed : ShuttleTimetableUiState
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreen.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreen.kt
index 2271eb38b..1da9c4f5a 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreen.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreen.kt
@@ -1,30 +1,42 @@
package `in`.koreatech.bus.screen.timetable.composable
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import `in`.koreatech.bus.screen.timetable.viewmodel.BusTimetableViewModel
+import `in`.koreatech.bus.util.goToArticle
+import `in`.koreatech.bus.state.ShuttleCourseRouteState
+import `in`.koreatech.bus.util.LocalOnRefresh
@Composable
internal fun BusTimetableScreen(
modifier: Modifier = Modifier,
onNavigationIconClick: () -> Unit = {},
+ onNavigateToShuttleTimetableScreen: (ShuttleCourseRouteState) -> Unit = {},
viewModel: BusTimetableViewModel = hiltViewModel(),
) {
val busTimetableUiState by viewModel.timetableUiState.collectAsStateWithLifecycle()
+ val busNoticeUiState by viewModel.noticeUiState.collectAsStateWithLifecycle()
- val shouldShowNotice by viewModel.shouldShowNotice.collectAsStateWithLifecycle()
- val notice by viewModel.notice.collectAsStateWithLifecycle()
+ val context = LocalContext.current
- BusTimetableScreenContent(
- modifier = modifier,
- busTimetableUiState = busTimetableUiState,
- onNavigationIconClick = onNavigationIconClick,
- shouldShowNotice = shouldShowNotice,
- notice = notice,
- onCloseNotice = viewModel::closeNotice
- )
+ CompositionLocalProvider(LocalOnRefresh provides viewModel::refresh) {
+ BusTimetableScreenContent(
+ modifier = modifier,
+ busTimetableUiState = busTimetableUiState,
+ busNoticeUiState = busNoticeUiState,
+ onNavigationIconClick = onNavigationIconClick,
+ onShuttleCourseRouteClick = onNavigateToShuttleTimetableScreen,
+ onExpressDirectionChange = viewModel::onExpressDirectionChanged,
+ onCityBusNumberChange = viewModel::onCityBusNumberChanged,
+ onCityDirectionChange = viewModel::onCityDirectionChanged,
+ onCloseNotice = viewModel::closeNotice,
+ onNoticeClick = { context.goToArticle(it.id) }
+ )
+ }
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreenContent.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreenContent.kt
index 5d6157404..b598caaa0 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreenContent.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/BusTimetableScreenContent.kt
@@ -1,6 +1,5 @@
package `in`.koreatech.bus.screen.timetable.composable
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -9,53 +8,66 @@ 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.lazy.LazyColumn
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.Close
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.CommonLoadingView
-import `in`.koreatech.bus.screen.timetable.type.BusType
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-import `in`.koreatech.bus.screen.timetable.type.ShuttleBusRouteType
+import `in`.koreatech.bus.component.CommonFailureView
+import `in`.koreatech.bus.component.CommonLoadingView
+import `in`.koreatech.bus.component.NoticeItem
+import `in`.koreatech.bus.mock.busNoticeUiStateMock
+import `in`.koreatech.bus.mock.cityTimetableMock
+import `in`.koreatech.bus.mock.expressTimetableMock
+import `in`.koreatech.bus.mock.shuttleCoursesMock
+import `in`.koreatech.bus.screen.timetable.viewmodel.BusNoticeUiState
import `in`.koreatech.bus.screen.timetable.viewmodel.BusTimetableUiState
-import `in`.koreatech.bus.viewstate.ArrivalViewState
-import `in`.koreatech.bus.viewstate.CommonTimetableViewState
-import `in`.koreatech.bus.viewstate.ShuttleRegionViewState
-import `in`.koreatech.bus.viewstate.ShuttleTimetableOverviewViewState
+import `in`.koreatech.bus.state.BusNoticeState
+import `in`.koreatech.bus.state.ShuttleCourseRouteState
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.bus.type.CityBusNumberType
+import `in`.koreatech.bus.type.CommonDirectionType
+import `in`.koreatech.bus.util.LocalSelectedTimetableTab
+import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.designsystem.component.tab.KoinTabRow
-import `in`.koreatech.koin.core.designsystem.component.text.LeadingIconText
import `in`.koreatech.koin.core.designsystem.component.topbar.KoinTopAppBar
-import `in`.koreatech.koin.core.designsystem.noRippleClickable
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
-import kotlinx.collections.immutable.toPersistentList
+import kotlinx.coroutines.launch
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun BusTimetableScreenContent(
busTimetableUiState: BusTimetableUiState,
- shouldShowNotice: Boolean,
- notice: String,
+ busNoticeUiState: BusNoticeUiState,
modifier: Modifier = Modifier,
+ onShuttleCourseRouteClick: (ShuttleCourseRouteState) -> Unit = {},
+ onExpressDirectionChange: (CommonDirectionType) -> Unit = {},
+ onCityBusNumberChange: (CityBusNumberType) -> Unit = {},
+ onCityDirectionChange: (CommonDirectionType) -> Unit = {},
onNavigationIconClick: () -> Unit = {},
onCloseNotice: () -> Unit = {},
+ onNoticeClick: (BusNoticeState) -> Unit = {},
previewTab: BusType = BusType.SHUTTLE
) {
@@ -64,8 +76,15 @@ internal fun BusTimetableScreenContent(
BusType.SHUTTLE -> stringResource(R.string.shuttle_timetable)
BusType.EXPRESS -> stringResource(R.string.express_timetable)
BusType.CITY -> stringResource(R.string.city_timetable)
+ else -> ""
}
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
+ val pagerState = rememberPagerState { BusType.entriesExceptAll.size }
+
+ var expressDirectionGuideText by rememberSaveable { mutableStateOf("") }
+
Column(
modifier = modifier
) {
@@ -74,100 +93,166 @@ internal fun BusTimetableScreenContent(
onNavigationIconClick = onNavigationIconClick
)
- LazyColumn {
- item {
- Column(
- modifier = Modifier.fillMaxWidth().background(Color.White).padding(horizontal = 24.dp)
- ) {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(Color.White)
+ .padding(horizontal = 24.dp)
+ ) {
+ Row {
Text(
text = busTypeHeadTitle,
style = KoinTheme.typography.bold20
)
- Spacer(modifier = Modifier.height(8.dp))
- LeadingIconText(
- text = stringResource(R.string.request_for_incorrect_information),
- iconRes = R.drawable.ic_caution
- )
- if (shouldShowNotice) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp)
- .background(
- color = KoinTheme.colors.info100,
- shape = RoundedCornerShape(8.dp)
- ).padding(16.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
+ if (pagerState.currentPage + 1 != BusType.SHUTTLE.ordinal) {
+ if (busTimetableUiState is BusTimetableUiState.Success) {
+ Spacer(modifier = Modifier.weight(1f))
Text(
- modifier = Modifier.weight(1f),
- text = notice,
- style = KoinTheme.typography.medium14,
- color = KoinTheme.colors.primary500,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
+ text = when (pagerState.currentPage + 1) {
+ BusType.EXPRESS.ordinal -> expressDirectionGuideText
+ BusType.CITY.ordinal -> context.getString(
+ R.string.ride,
+ busTimetableUiState.cityTimetable.busInfo.departNode
+ )
+ else -> ""
+ },
+ style = KoinTheme.typography.regular13.copy(color = KoinTheme.colors.primary500)
)
Icon(
- modifier = Modifier.padding(start = 4.dp).size(16.dp).noRippleClickable {
- onCloseNotice()
- },
- imageVector = Icons.Rounded.Close,
- contentDescription = notice,
- tint = KoinTheme.colors.neutral300
+ modifier = Modifier.padding(start = 4.dp),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_bus_station),
+ tint = KoinTheme.colors.primary500,
+ contentDescription = null
)
}
}
}
+ Spacer(modifier = Modifier.height(8.dp))
+ if (busNoticeUiState is BusNoticeUiState.Show) {
+ val eventValue = selectedTimetableTypeTab.getEventValue()
+ NoticeItem(
+ notice = busNoticeUiState.notice,
+ onCloseIconClick = {
+ onCloseNotice()
+ EventLogger.logCampusClickEvent(
+ "bus_announcement_close",
+ eventValue
+ )
+ },
+ onNoticeClick = {
+ EventLogger.logCampusClickEvent(
+ "bus_announcement",
+ eventValue
+ )
+ onNoticeClick(busNoticeUiState.notice)
+ }
+ )
+ }
}
- stickyHeader {
- KoinTabRow(
- titles = BusType.entries.map { stringResource(it.titleRes) },
- selectedTabIndex = selectedTimetableTypeTab.ordinal,
- onTabSelected = { selectedTimetableTypeTab = BusType.entries[it] }
- )
- }
+ KoinTabRow(
+ modifier = Modifier.padding(top = 8.dp),
+ titles = BusType.entriesExceptAll.map { stringResource(it.titleRes) },
+ selectedTabIndex = if (LocalInspectionMode.current)
+ previewTab.ordinal
+ else pagerState.currentPage,
+ onTabSelected = {
+ coroutineScope.launch {
+ pagerState.animateScrollToPage(it)
+ }
+ }
+ )
- item {
- if (LocalInspectionMode.current)
- selectedTimetableTypeTab = previewTab
+ if (LocalInspectionMode.current)
+ selectedTimetableTypeTab = previewTab
+ HorizontalPager(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White),
+ state = pagerState,
+ verticalAlignment = Alignment.Top
+ ) { page ->
when (busTimetableUiState) {
- is BusTimetableUiState.Success -> when (selectedTimetableTypeTab) {
- BusType.SHUTTLE -> {
- ShuttleTimetableScreen(
- modifier = Modifier.fillMaxSize()
- .background(KoinTheme.colors.neutral100),
- regions = busTimetableUiState.shuttleRegions.toPersistentList()
- )
- }
+ is BusTimetableUiState.Success -> {
+ when (page + 1) { // BusType.ALL 때문에 +1
+ BusType.SHUTTLE.ordinal -> {
+ CompositionLocalProvider(LocalSelectedTimetableTab provides BusType.SHUTTLE) {
+ ShuttleCoursesScreenContent(
+ modifier = Modifier
+ .background(KoinTheme.colors.neutral100)
+ .verticalScroll(rememberScrollState()),
+ shuttleCourses = busTimetableUiState.shuttleCourses,
+ onItemClicked = onShuttleCourseRouteClick,
+ )
+ }
+ }
- BusType.EXPRESS -> {
- ExpressTimetableScreen(
- modifier = Modifier.fillMaxSize()
- .background(KoinTheme.colors.neutral100),
- timetable = busTimetableUiState.expressTimetable
- )
- }
+ BusType.EXPRESS.ordinal -> {
+ CompositionLocalProvider(LocalSelectedTimetableTab provides BusType.EXPRESS) {
+ ExpressTimetableScreenContent(
+ modifier = Modifier
+ .background(KoinTheme.colors.neutral100)
+ .verticalScroll(rememberScrollState()),
+ expressTimetable = busTimetableUiState.expressTimetable,
+ onDirectionChanged = {
+ expressDirectionGuideText =
+ if (it == CommonDirectionType.TO_CHEONAN) context.getString(
+ R.string.guide_koreatech_station
+ ) else context.getString(R.string.guide_cheonan_station)
- BusType.CITY -> {
- CityTimetableContent(
- modifier = Modifier.fillMaxSize()
- .background(KoinTheme.colors.neutral100),
- timetable = busTimetableUiState.cityTimetable
- )
+ onExpressDirectionChange(it)
+ }
+ )
+ }
+ }
+
+ BusType.CITY.ordinal -> {
+ CompositionLocalProvider(LocalSelectedTimetableTab provides BusType.CITY) {
+ CityTimetableScreenContent(
+ modifier = Modifier
+ .background(KoinTheme.colors.neutral100)
+ .verticalScroll(rememberScrollState()),
+ timetable = busTimetableUiState.cityTimetable,
+ onBusNumberChanged = { onCityBusNumberChange(it) },
+ onDirectionChanged = { onCityDirectionChange(it) },
+ )
+ }
+ }
}
}
+
is BusTimetableUiState.Loading -> {
- CommonLoadingView(modifier = Modifier.fillMaxSize().padding(top = 100.dp))
+ CommonLoadingView(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 100.dp)
+ )
}
+
is BusTimetableUiState.LoadFailed -> {
- // TODO 로드 실패, Pull To Refresh 있으면 좋을 듯.
+ CommonFailureView(
+ modifier = Modifier.fillMaxSize()
+ )
}
}
}
}
}
+
+ LaunchedEffect(pagerState.targetPage) {
+ selectedTimetableTypeTab = BusType.entriesExceptAll[pagerState.targetPage]
+ }
+
+ LaunchedEffect(selectedTimetableTypeTab) {
+ EventLogger.logCampusClickEvent(
+ "timetable_bus_type_tab",
+ context.getString(selectedTimetableTypeTab.titleRes)
+ )
+ }
}
@Preview(showBackground = true)
@@ -177,8 +262,7 @@ private fun BusTimetableShuttleScreenPreview() {
modifier = Modifier.fillMaxSize(),
previewTab = BusType.SHUTTLE,
busTimetableUiState = previewUiState,
- shouldShowNotice = true,
- notice = "나랏말싸미 중국에 달라 어쩌구저쩌구 킹종대왕 갓종대왕"
+ busNoticeUiState = busNoticeUiStateMock
)
}
@Preview(showBackground = true)
@@ -188,8 +272,7 @@ private fun BusTimetableExpressScreenPreview() {
modifier = Modifier.fillMaxSize(),
previewTab = BusType.EXPRESS,
busTimetableUiState = previewUiState,
- shouldShowNotice = true,
- notice = "나랏말싸미 중국에 달라 어쩌구저쩌구 킹종대왕 갓종대왕"
+ busNoticeUiState = busNoticeUiStateMock
)
}
@Preview(showBackground = true)
@@ -199,8 +282,7 @@ private fun BusTimetableCityScreenPreview() {
modifier = Modifier.fillMaxSize(),
previewTab = BusType.CITY,
busTimetableUiState = previewUiState,
- shouldShowNotice = true,
- notice = "나랏말싸미 중국에 달라 어쩌구저쩌구 킹종대왕 갓종대왕"
+ busNoticeUiState = busNoticeUiStateMock
)
}
@Preview(showBackground = true)
@@ -210,130 +292,12 @@ private fun BusTimetableLoadingScreenPreview() {
modifier = Modifier.fillMaxSize(),
previewTab = BusType.CITY,
busTimetableUiState = BusTimetableUiState.Loading,
- shouldShowNotice = true,
- notice = "나랏말싸미 중국에 달라 어쩌구저쩌구 킹종대왕 갓종대왕"
+ busNoticeUiState = busNoticeUiStateMock
)
}
private val previewUiState = BusTimetableUiState.Success(
- shuttleRegions = listOf(
- ShuttleRegionViewState(
- name = "서울",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "서울-대전",
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대전",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대전-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대전-서울",
- description = "토요일, 일요일 운행"
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "대전-서울",
- description = "토요일, 천안아산역"
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대구",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대구-서울",
- description = "금요일 하교 추가"
- )
- )
- )
- ),
- expressTimetable = CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- ),
- ArrivalViewState(
- arrivalTime = "21:00"
- ),
- ArrivalViewState(
- arrivalTime = "21:30"
- ),
- ArrivalViewState(
- arrivalTime = "22:00"
- ),
- ArrivalViewState(
- arrivalTime = "22:30"
- ),
- ArrivalViewState(
- arrivalTime = "23:00"
- ),
- ArrivalViewState(
- arrivalTime = "23:30"
- )
- ),
- )
- ),
- cityTimetable = CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- )
- )
- )
- )
+ shuttleCourses = shuttleCoursesMock,
+ expressTimetable = expressTimetableMock,
+ cityTimetable = cityTimetableMock
)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableScreenContent.kt
similarity index 73%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableView.kt
rename to feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableScreenContent.kt
index 18be5242a..8263a9289 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CityTimetableScreenContent.kt
@@ -1,10 +1,12 @@
package `in`.koreatech.bus.screen.timetable.composable
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -15,25 +17,27 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.timetable.type.CityBusNumberType
-import `in`.koreatech.bus.screen.timetable.type.CommonDirectionType
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-import `in`.koreatech.bus.viewstate.ArrivalViewState
-import `in`.koreatech.bus.viewstate.CommonTimetableViewState
+import `in`.koreatech.bus.mock.cityTimetableMock
+import `in`.koreatech.bus.state.CityTimetableState
+import `in`.koreatech.bus.type.CityBusNumberType
+import `in`.koreatech.bus.type.CommonDirectionType
+import `in`.koreatech.bus.util.formatUpdatedTime
+import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.designsystem.component.chip.TextChipGroup
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
@Composable
-internal fun CityTimetableContent(
- timetable: CommonTimetableViewState,
+internal fun CityTimetableScreenContent(
+ timetable: CityTimetableState,
modifier: Modifier = Modifier,
onBusNumberChanged: (CityBusNumberType) -> Unit = {},
- onDirectionChanged: (CommonDirectionType) -> Unit = {}
+ onDirectionChanged: (CommonDirectionType) -> Unit = {},
) {
var selectedBusNumberType by rememberSaveable { mutableStateOf(CityBusNumberType.N400) }
@@ -61,8 +65,13 @@ internal fun CityTimetableContent(
onChipSelected = { title ->
selectedBusNumberType =
CityBusNumberType.entries.find { context.getString(it.titleRes) == title } ?: CityBusNumberType.N400
+ EventLogger.logCampusClickEvent(
+ "city_bus_route",
+ context.getString(selectedBusNumberType.titleRes)
+ )
},
- selectedChipIndexes = intArrayOf(selectedBusNumberType.ordinal)
+ selectedChipIndexes = intArrayOf(selectedBusNumberType.ordinal),
+ showClickRipple = false
)
}
Row(
@@ -83,15 +92,23 @@ internal fun CityTimetableContent(
onChipSelected = { title ->
selectedDirectionType =
CommonDirectionType.entries.find { context.getString(it.titleRes) == title } ?: CommonDirectionType.TO_BYEONGCHEON
+ EventLogger.logCampusClickEvent(
+ "city_bus_direction",
+ context.getString(selectedDirectionType.titleRes)
+ )
},
- selectedChipIndexes = intArrayOf(selectedDirectionType.ordinal)
+ selectedChipIndexes = intArrayOf(selectedDirectionType.ordinal),
+ showClickRipple = false
)
}
CommonTimetableView(
- timetable = timetable,
- modifier = Modifier.fillMaxSize()
+ timetable = timetable.departureTimes,
+ modifier = Modifier.fillMaxSize(),
+ updatedAt = timetable.updatedAt.formatUpdatedTime()
)
+
+ Box(modifier = Modifier.background(Color.White).fillMaxWidth().height(115.dp))
}
LaunchedEffect(selectedBusNumberType) {
@@ -106,30 +123,8 @@ internal fun CityTimetableContent(
@Preview
@Composable
private fun CityTimetableScreenPreview() {
- CityTimetableContent(
+ CityTimetableScreenContent(
modifier = Modifier.fillMaxSize().background(KoinTheme.colors.neutral100),
- timetable = CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- )
- )
- )
- )
+ timetable = cityTimetableMock
)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableItem.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableItem.kt
index fc5a1cbb9..bc1729742 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableItem.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableItem.kt
@@ -9,12 +9,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.viewstate.ArrivalViewState
+import `in`.koreatech.bus.state.DepartureState
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
@Composable
internal fun CommonTimetableItem(
- arrival: ArrivalViewState,
+ arrival: DepartureState,
textStyle: TextStyle,
modifier: Modifier = Modifier,
) {
@@ -23,7 +23,7 @@ internal fun CommonTimetableItem(
) {
Text(
modifier = Modifier.padding(vertical = 16.dp),
- text = arrival.arrivalTime,
+ text = arrival.departureTime,
style = textStyle,
)
HorizontalDivider(
@@ -36,8 +36,8 @@ internal fun CommonTimetableItem(
@Composable
private fun CommonTimetableItemPreview() {
CommonTimetableItem(
- arrival = ArrivalViewState(
- arrivalTime = "09:00"
+ arrival = DepartureState(
+ departureTime = "09:00"
),
textStyle = KoinTheme.typography.bold18.copy(
color = KoinTheme.colors.warning500
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableView.kt
index b2d8288e8..d8e03d40d 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/CommonTimetableView.kt
@@ -11,16 +11,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-import `in`.koreatech.bus.viewstate.ArrivalViewState
-import `in`.koreatech.bus.viewstate.CommonTimetableViewState
+import androidx.compose.ui.util.fastForEach
+import `in`.koreatech.bus.component.WrongInformationText
+import `in`.koreatech.bus.mock.commonTimetableMock
+import `in`.koreatech.bus.state.CommonTimetableState
+import `in`.koreatech.bus.util.LocalSelectedTimetableTab
import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
@Composable
internal fun CommonTimetableView(
- timetable: CommonTimetableViewState,
+ updatedAt: String,
+ timetable: CommonTimetableState,
modifier: Modifier = Modifier
) {
KoinSurface(
@@ -41,7 +44,7 @@ internal fun CommonTimetableView(
color = KoinTheme.colors.neutral600
)
- timetable.arrivals[DaytimeType.AM]?.forEach {
+ timetable.amDepartures.fastForEach {
CommonTimetableItem(
arrival = it,
textStyle = KoinTheme.typography.bold18.copy(
@@ -61,7 +64,7 @@ internal fun CommonTimetableView(
color = KoinTheme.colors.neutral600
)
- timetable.arrivals[DaytimeType.PM]?.forEach {
+ timetable.pmDepartures.fastForEach {
CommonTimetableItem(
arrival = it,
textStyle = KoinTheme.typography.bold18.copy(
@@ -75,10 +78,15 @@ internal fun CommonTimetableView(
Text(
modifier = Modifier.padding(vertical = 8.dp),
- text = stringResource(R.string.updated_at, timetable.updatedAt),
+ text = stringResource(R.string.updated_at, updatedAt),
style = KoinTheme.typography.regular14,
color = KoinTheme.colors.neutral500
)
+
+ WrongInformationText(
+ modifier = Modifier.padding(top = 4.dp),
+ loggingEventValue = LocalSelectedTimetableTab.current.getEventValue()
+ )
}
}
}
@@ -88,40 +96,7 @@ internal fun CommonTimetableView(
private fun CommonTimetableViewPreview() {
CommonTimetableView(
modifier = Modifier.fillMaxSize(),
- timetable = CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- ),
- ArrivalViewState(
- arrivalTime = "21:00"
- ),
- ArrivalViewState(
- arrivalTime = "21:30"
- ),
- ArrivalViewState(
- arrivalTime = "22:00"
- ),
- ArrivalViewState(
- arrivalTime = "22:30"
- ),
- )
- )
- )
+ timetable = commonTimetableMock,
+ updatedAt = "2024-11-11"
)
}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableScreenContent.kt
similarity index 58%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableView.kt
rename to feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableScreenContent.kt
index e975e919e..c6cc0f261 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableView.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ExpressTimetableScreenContent.kt
@@ -1,10 +1,12 @@
package `in`.koreatech.bus.screen.timetable.composable
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -15,21 +17,23 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.timetable.type.CommonDirectionType
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-import `in`.koreatech.bus.viewstate.ArrivalViewState
-import `in`.koreatech.bus.viewstate.CommonTimetableViewState
+import `in`.koreatech.bus.mock.expressTimetableMock
+import `in`.koreatech.bus.state.ExpressTimetableState
+import `in`.koreatech.bus.type.CommonDirectionType
+import `in`.koreatech.bus.util.formatUpdatedTime
+import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.designsystem.component.chip.TextChipGroup
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R
@Composable
-internal fun ExpressTimetableScreen(
- timetable: CommonTimetableViewState,
+internal fun ExpressTimetableScreenContent(
+ expressTimetable: ExpressTimetableState,
modifier: Modifier = Modifier,
onDirectionChanged: (CommonDirectionType) -> Unit = {}
) {
@@ -47,7 +51,7 @@ internal fun ExpressTimetableScreen(
verticalAlignment = Alignment.CenterVertically
) {
Text(
- text = stringResource(R.string.starting_point),
+ text = stringResource(R.string.operating),
style = KoinTheme.typography.regular16,
color = KoinTheme.colors.neutral600
)
@@ -58,15 +62,23 @@ internal fun ExpressTimetableScreen(
onChipSelected = { title ->
selectedDirectionType =
CommonDirectionType.entries.find { context.getString(it.titleRes) == title } ?: CommonDirectionType.TO_BYEONGCHEON
+ EventLogger.logCampusClickEvent(
+ "ds_bus_direction",
+ context.getString(selectedDirectionType.titleRes)
+ )
},
- selectedChipIndexes = intArrayOf(selectedDirectionType.ordinal)
+ selectedChipIndexes = intArrayOf(selectedDirectionType.ordinal),
+ showClickRipple = false
)
}
CommonTimetableView(
- timetable = timetable,
+ timetable = expressTimetable.timetable,
+ updatedAt = expressTimetable.updatedAt.formatUpdatedTime(),
modifier = Modifier.fillMaxSize()
)
+
+ Box(modifier = Modifier.background(Color.White).fillMaxWidth().height(115.dp))
}
LaunchedEffect(selectedDirectionType) {
@@ -77,48 +89,8 @@ internal fun ExpressTimetableScreen(
@Preview(showBackground = true)
@Composable
private fun ExpressTimetableScreenPreview() {
- ExpressTimetableScreen(
+ ExpressTimetableScreenContent(
modifier = Modifier.fillMaxSize().background(KoinTheme.colors.neutral100),
- timetable = CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- ),
- ArrivalViewState(
- arrivalTime = "21:00"
- ),
- ArrivalViewState(
- arrivalTime = "21:30"
- ),
- ArrivalViewState(
- arrivalTime = "22:00"
- ),
- ArrivalViewState(
- arrivalTime = "22:30"
- ),
- ArrivalViewState(
- arrivalTime = "23:00"
- ),
- ArrivalViewState(
- arrivalTime = "23:30"
- )
- ),
- )
- )
+ expressTimetable = expressTimetableMock
)
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleCoursesScreenContent.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleCoursesScreenContent.kt
new file mode 100644
index 000000000..770b5889d
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleCoursesScreenContent.kt
@@ -0,0 +1,204 @@
+package `in`.koreatech.bus.screen.timetable.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import `in`.koreatech.bus.component.ShuttleBusOperationChip
+import `in`.koreatech.bus.component.WrongInformationText
+import `in`.koreatech.bus.mock.shuttleCoursesMock
+import `in`.koreatech.bus.state.ShuttleCourseRegionState
+import `in`.koreatech.bus.state.ShuttleCourseRouteState
+import `in`.koreatech.bus.state.ShuttleCoursesState
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+import `in`.koreatech.bus.util.LocalSelectedTimetableTab
+import `in`.koreatech.bus.util.formatPeriod
+import `in`.koreatech.koin.core.analytics.EventLogger
+import `in`.koreatech.koin.core.designsystem.component.chip.TextChipGroup
+import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface
+import `in`.koreatech.koin.core.designsystem.component.text.LeadingIconText
+import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
+import `in`.koreatech.koin.feature.bus.R
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
+
+@Composable
+internal fun ShuttleCoursesScreenContent(
+ shuttleCourses: ShuttleCoursesState,
+ modifier: Modifier = Modifier,
+ onItemClicked: (ShuttleCourseRouteState) -> Unit = {},
+) {
+
+ var selectedRouteType by rememberSaveable { mutableStateOf(ShuttleBusOperationType.ALL) }
+ val context = LocalContext.current
+
+ Column(
+ modifier = modifier
+ ) {
+ TextChipGroup(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp, horizontal = 24.dp),
+ titles = ShuttleBusOperationType.entries.map { stringResource(it.titleRes) },
+ onChipSelected = { title ->
+ selectedRouteType = ShuttleBusOperationType.entries.find { context.getString(it.titleRes) == title } ?: ShuttleBusOperationType.ALL
+ EventLogger.logCampusClickEvent(
+ "shuttle_bus_route",
+ context.getString(selectedRouteType.titleRes)
+ )
+ },
+ selectedChipIndexes = intArrayOf(selectedRouteType.ordinal),
+ showClickRipple = false
+ )
+ shuttleCourses.courses.forEach { courseEntry ->
+ val filteredValue = courseEntry.value.filter { courseRouteState ->
+ if (selectedRouteType == ShuttleBusOperationType.ALL) true
+ else courseRouteState.type == selectedRouteType
+ }
+
+ if (filteredValue.isNotEmpty()) {
+ if (courseEntry.key != shuttleCourses.courses.keys.first())
+ Spacer(modifier = Modifier.height(10.dp))
+
+ ShuttleCourseView(
+ modifier = Modifier,
+ region = courseEntry.key,
+ shuttleCourseRoutes = filteredValue.toImmutableList(),
+ onItemClicked = {
+ EventLogger.logCampusClickEvent(
+ "area_specific_route",
+ "${context.getString(it.type.simpleTitleRes)}_${it.routeName}"
+ )
+ onItemClicked(it)
+ }
+ )
+ }
+ }
+
+ Column(modifier = Modifier
+ .background(Color.White)
+ .padding(top = 8.dp)
+ .fillMaxWidth()) {
+ Text(
+ modifier = Modifier.padding(horizontal = 24.dp),
+ text = stringResource(R.string.timetable_semester_information,
+ shuttleCourses.semester.name,
+ shuttleCourses.semester.from.formatPeriod(),
+ shuttleCourses.semester.to.formatPeriod()
+ ), style = KoinTheme.typography.regular14,
+ color = KoinTheme.colors.neutral500,
+ )
+ WrongInformationText(
+ modifier = Modifier.padding(top = 4.dp, start = 24.dp),
+ loggingEventValue = LocalSelectedTimetableTab.current.getEventValue()
+ )
+ Spacer(modifier = Modifier
+ .fillMaxWidth()
+ .height(120.dp))
+ }
+ }
+}
+
+@Composable
+private fun ShuttleCourseView(
+ modifier: Modifier = Modifier,
+ region: ShuttleCourseRegionState,
+ shuttleCourseRoutes: ImmutableList,
+ onItemClicked: (ShuttleCourseRouteState) -> Unit = {}
+) {
+ KoinSurface(
+ modifier = modifier
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp)
+ .padding(top = 16.dp, bottom = 4.dp)
+ ) {
+ Text(
+ text = region.name,
+ style = KoinTheme.typography.bold18,
+ )
+ shuttleCourseRoutes.forEach {
+ ShuttleRouteItem(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ onItemClicked(it)
+ }
+ .padding(horizontal = 8.dp, vertical = 10.dp),
+ shuttleCourseRoute = it,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ShuttleRouteItem(
+ shuttleCourseRoute: ShuttleCourseRouteState,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ ShuttleBusOperationChip(
+ operationType = shuttleCourseRoute.type,
+ )
+
+ Text(
+ text = shuttleCourseRoute.routeName,
+ style = KoinTheme.typography.medium16,
+ modifier = Modifier.padding(start = 8.dp)
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
+ contentDescription = shuttleCourseRoute.routeName,
+ tint = KoinTheme.colors.neutral400
+ )
+ }
+ if (shuttleCourseRoute.subName.isNotEmpty())
+ Text(
+ text = shuttleCourseRoute.subName,
+ style = KoinTheme.typography.regular12,
+ color = KoinTheme.colors.neutral500,
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ShuttleCoursesScreenPreview() {
+ ShuttleCoursesScreenContent(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(KoinTheme.colors.neutral100),
+ shuttleCourses = shuttleCoursesMock
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleTimetableView.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleTimetableView.kt
deleted file mode 100644
index 5367258eb..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/composable/ShuttleTimetableView.kt
+++ /dev/null
@@ -1,199 +0,0 @@
-package `in`.koreatech.bus.screen.timetable.composable
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import `in`.koreatech.bus.screen.timetable.type.ShuttleBusRouteType
-import `in`.koreatech.bus.viewstate.ShuttleRegionViewState
-import `in`.koreatech.bus.viewstate.ShuttleTimetableOverviewViewState
-import `in`.koreatech.koin.core.designsystem.component.chip.ReadOnlyTextChip
-import `in`.koreatech.koin.core.designsystem.component.chip.TextChipGroup
-import `in`.koreatech.koin.core.designsystem.component.tab.KoinSurface
-import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
-import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.persistentListOf
-
-@Composable
-internal fun ShuttleTimetableScreen(
- modifier: Modifier = Modifier,
- regions: ImmutableList
-) {
-
- var selectedRouteType by rememberSaveable { mutableStateOf(ShuttleBusRouteType.ALL) }
- val context = LocalContext.current
-
- Column(
- modifier = modifier
- ) {
- TextChipGroup(
- modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 24.dp),
- titles = ShuttleBusRouteType.entries.map { stringResource(it.titleRes) },
- onChipSelected = { title ->
- selectedRouteType = ShuttleBusRouteType.entries.find { context.getString(it.titleRes) == title } ?: ShuttleBusRouteType.ALL
- },
- selectedChipIndexes = intArrayOf(selectedRouteType.ordinal)
- )
- regions.forEach {
- ShuttleRegionView(
- modifier = Modifier,
- region = it
- )
- if (it != regions.last()) {
- Spacer(modifier = Modifier.height(10.dp))
- }
- }
- }
-}
-
-@Composable
-private fun ShuttleRegionView(
- modifier: Modifier = Modifier,
- region: ShuttleRegionViewState
-) {
- KoinSurface(
- modifier = modifier
- ) {
- Column(
- modifier = Modifier.padding(horizontal = 24.dp).padding(top = 16.dp, bottom = 4.dp)
- ) {
- Text(
- text = region.name,
- style = KoinTheme.typography.bold18,
- )
- region.timetableOverviews.forEach {
- ShuttleRouteItem(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 10.dp),
- timetableOverview = it
- )
- }
- }
- }
-}
-
-@Composable
-private fun ShuttleRouteItem(
- modifier: Modifier = Modifier,
- timetableOverview: ShuttleTimetableOverviewViewState
-) {
- Column(
- modifier = modifier
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- ReadOnlyTextChip(
- title = stringResource(timetableOverview.routeType.simpleTitleRes),
- containerColor = when (timetableOverview.routeType) { // TODO : 색상 정리
- ShuttleBusRouteType.WEEKDAY -> Color(0xFF34ADFF)
- ShuttleBusRouteType.WEEKEND -> Color(0xFFFFB443)
- ShuttleBusRouteType.CIRCULATION -> Color(0xFF4ED92C)
- else -> Color.Transparent
- },
- textStyle = KoinTheme.typography.regular12.copy(color = Color.White)
- )
-
- Text(
- text = timetableOverview.name,
- style = KoinTheme.typography.medium16,
- modifier = Modifier.padding(start = 8.dp)
- )
- Spacer(modifier = Modifier.weight(1f))
- Icon(
- imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
- contentDescription = timetableOverview.name,
- tint = KoinTheme.colors.neutral400
- )
- }
- if (timetableOverview.description.isNotEmpty())
- Text(
- text = timetableOverview.description,
- style = KoinTheme.typography.regular12,
- color = KoinTheme.colors.neutral500,
- )
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun ShuttleTimetableScreenPreview() {
- ShuttleTimetableScreen(
- modifier = Modifier.fillMaxSize().background(KoinTheme.colors.neutral100),
- regions = persistentListOf(
- ShuttleRegionViewState(
- name = "서울",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "서울-대전",
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대전",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대전-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대전-서울",
- description = "대전에서 서울로 이동하는 노선입니다."
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "대전-서울",
- description = "대전에서 서울로 이동하는 노선입니다."
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대구",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대구-서울",
- )
- )
- )
- )
- )
-}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/BusType.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/BusType.kt
deleted file mode 100644
index b1ab1b6ee..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/BusType.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package `in`.koreatech.bus.screen.timetable.type
-
-import android.os.Parcelable
-import androidx.annotation.StringRes
-import `in`.koreatech.koin.feature.bus.R
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class BusType(
- @StringRes val titleRes: Int
-) : Parcelable {
- SHUTTLE(R.string.tab_shuttle),
- EXPRESS(R.string.tab_express),
- CITY(R.string.tab_city),
-}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CityBusNumberType.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CityBusNumberType.kt
deleted file mode 100644
index 5fa54bd36..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CityBusNumberType.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package `in`.koreatech.bus.screen.timetable.type
-
-import android.os.Parcelable
-import androidx.annotation.StringRes
-import `in`.koreatech.koin.feature.bus.R
-import kotlinx.parcelize.Parcelize
-
-@Parcelize
-enum class CityBusNumberType(
- @StringRes val titleRes: Int
-) : Parcelable {
- N400(R.string.n400), N405(R.string.n405), N495(R.string.n495)
-}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/DaytimeType.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/DaytimeType.kt
deleted file mode 100644
index a39fc6a8f..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/DaytimeType.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package `in`.koreatech.bus.screen.timetable.type
-
-enum class DaytimeType {
- AM, PM
-}
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/viewmodel/BusTimetableViewModel.kt b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/viewmodel/BusTimetableViewModel.kt
index 03588c054..da1f492eb 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/viewmodel/BusTimetableViewModel.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/viewmodel/BusTimetableViewModel.kt
@@ -1,161 +1,86 @@
package `in`.koreatech.bus.screen.timetable.viewmodel
-import androidx.lifecycle.ViewModel
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-import `in`.koreatech.bus.screen.timetable.type.ShuttleBusRouteType
-import `in`.koreatech.bus.viewstate.ArrivalViewState
-import `in`.koreatech.bus.viewstate.CommonTimetableViewState
-import `in`.koreatech.bus.viewstate.ShuttleRegionViewState
-import `in`.koreatech.bus.viewstate.ShuttleTimetableOverviewViewState
+import `in`.koreatech.bus.BaseBusViewModel
+import `in`.koreatech.bus.mock.busNoticeMock
+import `in`.koreatech.bus.state.BusNoticeState
+import `in`.koreatech.bus.state.CityTimetableState
+import `in`.koreatech.bus.state.ExpressTimetableState
+import `in`.koreatech.bus.state.ShuttleCoursesState
+import `in`.koreatech.bus.state.toBusNoticeState
+import `in`.koreatech.bus.state.toCityTimetableState
+import `in`.koreatech.bus.state.toExpressTimetableState
+import `in`.koreatech.bus.state.toShuttleCoursesState
+import `in`.koreatech.bus.type.CityBusNumberType
+import `in`.koreatech.bus.type.CommonDirectionType
import `in`.koreatech.koin.core.onboarding.OnboardingManager
import `in`.koreatech.koin.core.onboarding.OnboardingType
-import kotlinx.coroutines.async
+import `in`.koreatech.koin.domain.repository.BusRepository
+import `in`.koreatech.koin.feature.bus.BuildConfig
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class BusTimetableViewModel @Inject constructor(
- private val onboardingManager: OnboardingManager
-) : ViewModel() {
-
- /** 임시 데이터 모음 */
- val timetableUiState = flow {
- val shuttleRegions = viewModelScope.async {
- listOf(
- ShuttleRegionViewState(
- name = "서울",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "서울-대전",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "서울-대전",
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대전",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대전-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대전-서울",
- description = "토요일, 일요일 운행"
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.CIRCULATION,
- name = "대전-서울",
- description = "토요일, 천안아산역"
- )
- )
- ),
- ShuttleRegionViewState(
- name = "대구",
- timetableOverviews = listOf(
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKDAY,
- name = "대구-서울",
- ),
- ShuttleTimetableOverviewViewState(
- routeType = ShuttleBusRouteType.WEEKEND,
- name = "대구-서울",
- description = "금요일 하교 추가"
- )
- )
- )
- )
+ private val savedStateHandle: SavedStateHandle,
+ private val onboardingManager: OnboardingManager,
+ private val busRepository: BusRepository
+) : BaseBusViewModel() {
+
+ // 셔틀버스 노선 필터링은 Composable 내에서 처리 중 [ShuttleCoursesScreenContent.kt]
+ private val expressDirection = savedStateHandle.getStateFlow(KEY_EXPRESS_DIRECTION, CommonDirectionType.TO_BYEONGCHEON)
+ private val cityNumber = savedStateHandle.getStateFlow(KEY_CITY_NUMBER, CityBusNumberType.N400)
+ private val cityDirection = savedStateHandle.getStateFlow(KEY_CITY_DIRECTION, CommonDirectionType.TO_BYEONGCHEON)
+
+ private val shuttleCourses = refreshToggle.transform {
+ busRepository.fetchShuttleCourses().onSuccess {
+ emit(it.toShuttleCoursesState())
+ }.onFailure {
+ emit(null)
}
- val expressTimetable = viewModelScope.async {
- CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- ),
- ArrivalViewState(
- arrivalTime = "21:00"
- ),
- ArrivalViewState(
- arrivalTime = "21:30"
- ),
- ArrivalViewState(
- arrivalTime = "22:00"
- ),
- ArrivalViewState(
- arrivalTime = "22:30"
- ),
- ArrivalViewState(
- arrivalTime = "23:00"
- ),
- ArrivalViewState(
- arrivalTime = "23:30"
- )
- ),
- )
- )
+ }
+
+ private val expressTimetable = combineTransform(expressDirection, refreshToggle) { direction, _ ->
+ val directionQuery = when (direction) {
+ CommonDirectionType.TO_BYEONGCHEON -> "to"
+ CommonDirectionType.TO_CHEONAN -> "from"
}
- val cityTimetable = viewModelScope.async {
- CommonTimetableViewState(
- updatedAt = "2024-09-21",
- arrivals = mapOf(
- DaytimeType.AM to listOf(
- ArrivalViewState(
- arrivalTime = "09:00"
- ),
- ArrivalViewState(
- arrivalTime = "09:30"
- ),
- ArrivalViewState(
- arrivalTime = "10:00"
- ),
- ArrivalViewState(
- arrivalTime = "10:30"
- ),
- ), DaytimeType.PM to listOf(
- ArrivalViewState(
- arrivalTime = "14:30"
- )
- )
- )
- )
+ busRepository.fetchExpressTimetable(directionQuery).onSuccess {
+ emit(it.toExpressTimetableState())
+ }.onFailure {
+ emit(null)
}
+ }
- emit(BusTimetableUiState.Success(
- shuttleRegions.await(), expressTimetable.await(), cityTimetable.await()
- ))
+ private val cityTimetable = combineTransform(cityNumber, cityDirection, refreshToggle) { busNumber, direction, _ ->
+ val directionQuery = when(direction) {
+ CommonDirectionType.TO_BYEONGCHEON -> busNumber.toByeongcheonQuery
+ CommonDirectionType.TO_CHEONAN -> busNumber.toCheonanQuery
+ }
+ busRepository.fetchCityTimetable(busNumber.numberQuery, directionQuery).onSuccess {
+ emit(it.toCityTimetableState())
+ }.onFailure {
+ emit(null)
+ }
+ }
+
+ val timetableUiState = combine(
+ shuttleCourses,
+ expressTimetable,
+ cityTimetable,
+ ) { shuttleCourses, expressTimetable, cityTimetable ->
+ if (shuttleCourses == null || expressTimetable == null || cityTimetable == null)
+ BusTimetableUiState.LoadFailed
+ else BusTimetableUiState.Success(shuttleCourses, expressTimetable, cityTimetable)
}.catch {
emit(BusTimetableUiState.LoadFailed)
}.stateIn(
@@ -164,35 +89,80 @@ class BusTimetableViewModel @Inject constructor(
initialValue = BusTimetableUiState.Loading
)
- val notice = flow {
- emit("[긴급] 9.27(금) 대학등교방향 천안셔틀버스 터미널 미정차 알림(천안역에서 승차바람)")
- }.stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(5_000),
- initialValue = "[긴급] 9.27(금) 대학등교방향 천안셔틀버스 터미널 미정차 알림(천안역에서 승차바람)"
+ private val shouldShowNotice = onboardingManager.getShouldOnboardFlow(
+ OnboardingType.SHOW_BUS_HEAD_ARTICLE
)
- val shouldShowNotice = onboardingManager.getShouldOnboardFlow(
- OnboardingType.SHOW_BUS_HEAD_ARTICLE
- ).stateIn(
+ private val notice = flow {
+ busRepository.fetchBusNotice().onSuccess {
+ emit(it.toBusNoticeState())
+ }.onFailure {
+ if (BuildConfig.DEBUG) emit(busNoticeMock)
+ else emit(null)
+ }
+ }
+
+ val noticeUiState = combine(notice, shouldShowNotice) { notice, shouldShow ->
+ if (notice == null)
+ BusNoticeUiState.LoadFailed
+ else {
+ val lastNoticeId = busRepository.getLastShownNoticeId().getOrElse { -1 }
+
+ busRepository.saveLastShownNoticeId(notice.id).getOrNull() ?: return@combine BusNoticeUiState.LoadFailed
+ if (notice.id == lastNoticeId) {
+ if (shouldShow)
+ BusNoticeUiState.Show(notice)
+ else
+ BusNoticeUiState.NotShow
+ } else {
+ onboardingManager.updateShouldOnboard(OnboardingType.SHOW_BUS_HEAD_ARTICLE, true)
+ BusNoticeUiState.Show(notice)
+ }
+ }
+ }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
- initialValue = false
+ initialValue = BusNoticeUiState.Loading
)
+ fun onExpressDirectionChanged(direction: CommonDirectionType) {
+ savedStateHandle[KEY_EXPRESS_DIRECTION] = direction
+ }
+
+ fun onCityBusNumberChanged(number: CityBusNumberType) {
+ savedStateHandle[KEY_CITY_NUMBER] = number
+ }
+
+ fun onCityDirectionChanged(direction: CommonDirectionType) {
+ savedStateHandle[KEY_CITY_DIRECTION] = direction
+ }
+
fun closeNotice() {
viewModelScope.launch {
onboardingManager.updateShouldOnboard(OnboardingType.SHOW_BUS_HEAD_ARTICLE, false)
}
}
+
+ companion object {
+ private const val KEY_EXPRESS_DIRECTION = "express_direction"
+ private const val KEY_CITY_NUMBER = "city_number"
+ private const val KEY_CITY_DIRECTION = "city_direction"
+ }
}
sealed interface BusTimetableUiState {
data class Success(
- val shuttleRegions: List,
- val expressTimetable: CommonTimetableViewState,
- val cityTimetable: CommonTimetableViewState
+ val shuttleCourses: ShuttleCoursesState,
+ val expressTimetable: ExpressTimetableState,
+ val cityTimetable: CityTimetableState
) : BusTimetableUiState
data object Loading : BusTimetableUiState
data object LoadFailed: BusTimetableUiState
+}
+
+sealed interface BusNoticeUiState {
+ data class Show(val notice: BusNoticeState) : BusNoticeUiState
+ data object Loading : BusNoticeUiState
+ data object LoadFailed : BusNoticeUiState
+ data object NotShow : BusNoticeUiState
}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/BusNoticeState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/BusNoticeState.kt
new file mode 100644
index 000000000..b7b5e40f9
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/BusNoticeState.kt
@@ -0,0 +1,13 @@
+package `in`.koreatech.bus.state
+
+import `in`.koreatech.koin.domain.model.bus.BusNotice
+
+data class BusNoticeState(
+ val id: Int,
+ val title: String,
+)
+
+fun BusNotice.toBusNoticeState() = BusNoticeState(
+ id = id,
+ title = title,
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/BusSearchResultState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/BusSearchResultState.kt
new file mode 100644
index 000000000..15e35a8b9
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/BusSearchResultState.kt
@@ -0,0 +1,19 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.bus.type.BusType
+import `in`.koreatech.koin.domain.model.bus.BusSearchResult
+import java.time.LocalTime
+
+@Immutable
+data class BusSearchResultState(
+ val busType: BusType,
+ val busName: String,
+ val departureTime: LocalTime
+)
+
+fun BusSearchResult.toBusSearchResultState() = BusSearchResultState(
+ busType = BusType.valueOf(busType.uppercase()),
+ busName = busName,
+ departureTime = departureTime
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/CityBusInfoState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/CityBusInfoState.kt
new file mode 100644
index 000000000..385869688
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/CityBusInfoState.kt
@@ -0,0 +1,15 @@
+package `in`.koreatech.bus.state
+
+import `in`.koreatech.koin.domain.model.bus.CityBusInfo
+
+data class CityBusInfoState(
+ val number: Int,
+ val departNode: String,
+ val arriveNode: String,
+)
+
+fun CityBusInfo.toCityBusInfoState() = CityBusInfoState(
+ number = number,
+ departNode = departNode,
+ arriveNode = arriveNode,
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/CityTimetableState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/CityTimetableState.kt
new file mode 100644
index 000000000..d3d24b5c2
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/CityTimetableState.kt
@@ -0,0 +1,24 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.koin.domain.model.bus.CityTimetable
+import `in`.koreatech.koin.domain.model.bus.CityTimetableItem
+import java.time.LocalDateTime
+
+@Immutable
+data class CityTimetableState(
+ val departureTimes: CommonTimetableState,
+ val busInfo: CityBusInfoState,
+ val updatedAt: LocalDateTime
+)
+
+fun CityTimetable.toCityTimetableState() = CityTimetableState(
+ departureTimes = timetable[0].mapToCommonTimetableState(),
+ busInfo = busInfo.toCityBusInfoState(),
+ updatedAt = updatedAt
+)
+
+private fun CityTimetableItem.mapToCommonTimetableState() = CommonTimetableState(
+ amDepartures = this.departureTimes.filter { it.split(":")[0].toInt() < 12 }.map { DepartureState(it) },
+ pmDepartures = this.departureTimes.filter { it.split(":")[0].toInt() >= 12 }.map { DepartureState(it) },
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/CommonTimetableState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/CommonTimetableState.kt
new file mode 100644
index 000000000..50e64b915
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/CommonTimetableState.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class CommonTimetableState(
+ val amDepartures: List,
+ val pmDepartures: List,
+)
+
+@JvmInline
+value class DepartureState(
+ val departureTime: String,
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ExpressTimetableState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ExpressTimetableState.kt
new file mode 100644
index 000000000..327fdf6fb
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ExpressTimetableState.kt
@@ -0,0 +1,22 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetable
+import `in`.koreatech.koin.domain.model.bus.ExpressTimetableItem
+import java.time.LocalDateTime
+
+@Immutable
+data class ExpressTimetableState(
+ val timetable: CommonTimetableState,
+ val updatedAt: LocalDateTime,
+)
+
+fun ExpressTimetable.toExpressTimetableState() = ExpressTimetableState(
+ timetable = timetable.mapToCommonTimetableState(),
+ updatedAt = updatedAt,
+)
+
+private fun List.mapToCommonTimetableState() = CommonTimetableState(
+ amDepartures = this.filter { it.departureTime.split(":")[0].toInt() < 12 }.map { DepartureState(it.departureTime) },
+ pmDepartures = this.filter { it.departureTime.split(":")[0].toInt() >= 12 }.map { DepartureState(it.departureTime) },
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ImmutableLocalTime.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ImmutableLocalTime.kt
new file mode 100644
index 000000000..36cec9fd3
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ImmutableLocalTime.kt
@@ -0,0 +1,9 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import java.time.LocalTime
+
+@Immutable
+data class ImmutableLocalTime(
+ val localTime: LocalTime
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRegionState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRegionState.kt
new file mode 100644
index 000000000..72e1aa4f4
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRegionState.kt
@@ -0,0 +1,4 @@
+package `in`.koreatech.bus.state
+
+@JvmInline
+value class ShuttleCourseRegionState(val name: String)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRouteState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRouteState.kt
new file mode 100644
index 000000000..81f732360
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCourseRouteState.kt
@@ -0,0 +1,24 @@
+package `in`.koreatech.bus.state
+
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+import `in`.koreatech.bus.util.CIRCULATION
+import `in`.koreatech.bus.util.WEEKEND
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourseRoute
+
+data class ShuttleCourseRouteState(
+ val id: String,
+ val type: ShuttleBusOperationType,
+ val routeName: String,
+ val subName: String
+)
+
+fun ShuttleCourseRoute.toShuttleCourseRouteState() = ShuttleCourseRouteState(
+ id = id,
+ type = when(type) {
+ CIRCULATION -> ShuttleBusOperationType.CIRCULATION
+ WEEKEND -> ShuttleBusOperationType.WEEKEND
+ else -> ShuttleBusOperationType.WEEKDAY
+ },
+ routeName = routeName,
+ subName = subName
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCoursesState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCoursesState.kt
new file mode 100644
index 000000000..b236d245f
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleCoursesState.kt
@@ -0,0 +1,27 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.koin.domain.model.bus.ShuttleCourses
+
+@Immutable
+data class ShuttleCoursesState(
+ val courses: Map>,
+ val semester: ShuttleSemesterState
+) {
+
+ companion object {
+ val EMPTY = ShuttleCoursesState(
+ courses = emptyMap(),
+ semester = ShuttleSemesterState.EMPTY
+ )
+ }
+}
+
+fun ShuttleCourses.toShuttleCoursesState() = ShuttleCoursesState(
+ courses = courses.associate { shuttleCourse ->
+ ShuttleCourseRegionState(shuttleCourse.region) to shuttleCourse.routes.map {
+ shuttleCourseRoute -> shuttleCourseRoute.toShuttleCourseRouteState()
+ }
+ },
+ semester = semester.toShuttleSemesterState()
+)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleSemesterState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleSemesterState.kt
new file mode 100644
index 000000000..6e781e38d
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleSemesterState.kt
@@ -0,0 +1,25 @@
+package `in`.koreatech.bus.state
+
+import `in`.koreatech.koin.domain.model.bus.ShuttleSemester
+import java.time.LocalDate
+
+data class ShuttleSemesterState(
+ val name: String,
+ val from: LocalDate,
+ val to: LocalDate
+) {
+
+ companion object {
+ val EMPTY = ShuttleSemesterState(
+ name = "",
+ from = LocalDate.now(),
+ to = LocalDate.now()
+ )
+ }
+}
+
+fun ShuttleSemester.toShuttleSemesterState() = ShuttleSemesterState(
+ name = name,
+ from = from,
+ to = to
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableNodeInfoState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableNodeInfoState.kt
new file mode 100644
index 000000000..c413cca2b
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableNodeInfoState.kt
@@ -0,0 +1,13 @@
+package `in`.koreatech.bus.state
+
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetableNodeInfo
+
+data class ShuttleTimetableNodeInfoState(
+ val name: String,
+ val detail: String,
+)
+
+fun ShuttleTimetableNodeInfo.toShuttleTimetableNodeInfoState() = ShuttleTimetableNodeInfoState(
+ name = name,
+ detail = detail
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableRouteInfoState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableRouteInfoState.kt
new file mode 100644
index 000000000..833226e4b
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableRouteInfoState.kt
@@ -0,0 +1,15 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetableRouteInfo
+
+@Immutable
+data class ShuttleTimetableRouteInfoState(
+ val name: String,
+ val arrivalTimes: List,
+)
+
+fun ShuttleTimetableRouteInfo.toShuttleTimetableRouteInfoState() = ShuttleTimetableRouteInfoState(
+ name = name,
+ arrivalTimes = arrivalTimes
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableState.kt b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableState.kt
new file mode 100644
index 000000000..66dd343af
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/state/ShuttleTimetableState.kt
@@ -0,0 +1,38 @@
+package `in`.koreatech.bus.state
+
+import androidx.compose.runtime.Immutable
+import `in`.koreatech.bus.type.ShuttleBusOperationType
+import `in`.koreatech.bus.util.CIRCULATION
+import `in`.koreatech.bus.util.WEEKEND
+import `in`.koreatech.koin.domain.model.bus.ShuttleTimetable
+
+@Immutable
+data class ShuttleTimetableState(
+ val region: String,
+ val routeType: ShuttleBusOperationType,
+ val routeName: String,
+ val subTitle: String,
+ val nodeInfo: List,
+ val routeInfo: List,
+) {
+
+ /**
+ * 등교 하교 시간표가 모두 있는 경우에만 탭 표시
+ */
+ fun showTabs(): Boolean {
+ return routeType == ShuttleBusOperationType.WEEKDAY && routeInfo.size == 2
+ }
+}
+
+fun ShuttleTimetable.toShuttleTimetableState() = ShuttleTimetableState(
+ region = region,
+ routeType = when (routeType) {
+ CIRCULATION -> ShuttleBusOperationType.CIRCULATION
+ WEEKEND -> ShuttleBusOperationType.WEEKEND
+ else -> ShuttleBusOperationType.WEEKDAY
+ },
+ routeName = routeName,
+ subTitle = subTitle,
+ nodeInfo = nodeInfo.map { it.toShuttleTimetableNodeInfoState() },
+ routeInfo = routeInfo.map { it.toShuttleTimetableRouteInfoState() }
+)
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/type/BusType.kt b/feature/bus/src/main/java/in/koreatech/bus/type/BusType.kt
new file mode 100644
index 000000000..a8b89c5cf
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/BusType.kt
@@ -0,0 +1,32 @@
+package `in`.koreatech.bus.type
+
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.res.stringResource
+import `in`.koreatech.koin.feature.bus.R
+
+// property 이름 바꾸면 안됨!!
+enum class BusType(
+ @StringRes val titleRes: Int
+) {
+ ALL(R.string.all_bus_type),
+ SHUTTLE(R.string.tab_shuttle),
+ EXPRESS(R.string.tab_express),
+ CITY(R.string.tab_city);
+
+ @Composable
+ @ReadOnlyComposable
+ fun getEventValue(): String {
+ return when(this) {
+ SHUTTLE -> stringResource(R.string.shuttle_timetable)
+ EXPRESS -> stringResource(R.string.express_timetable)
+ CITY -> stringResource(R.string.city_timetable)
+ else -> ""
+ }
+ }
+
+ companion object {
+ val entriesExceptAll = values().filter { it != ALL }
+ }
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/type/CityBusNumberType.kt b/feature/bus/src/main/java/in/koreatech/bus/type/CityBusNumberType.kt
new file mode 100644
index 000000000..b7f12c567
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/CityBusNumberType.kt
@@ -0,0 +1,18 @@
+package `in`.koreatech.bus.type
+
+import android.os.Parcelable
+import androidx.annotation.StringRes
+import `in`.koreatech.koin.feature.bus.R
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+enum class CityBusNumberType(
+ @StringRes val titleRes: Int,
+ val numberQuery: Int,
+ val toByeongcheonQuery: String,
+ val toCheonanQuery: String,
+) : Parcelable {
+ N400(R.string.n400, 400, "병천3리", "종합터미널"),
+ N402(R.string.n402, 402, "황사동", "종합터미널"),
+ N405(R.string.n405, 405, "유관순열사사적지", "종합터미널"),
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CommonDirectionType.kt b/feature/bus/src/main/java/in/koreatech/bus/type/CommonDirectionType.kt
similarity index 86%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CommonDirectionType.kt
rename to feature/bus/src/main/java/in/koreatech/bus/type/CommonDirectionType.kt
index 6997f09c3..a222efb7a 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/CommonDirectionType.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/CommonDirectionType.kt
@@ -1,4 +1,4 @@
-package `in`.koreatech.bus.screen.timetable.type
+package `in`.koreatech.bus.type
import android.os.Parcelable
import androidx.annotation.StringRes
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceSelectMode.kt b/feature/bus/src/main/java/in/koreatech/bus/type/PlaceSelectMode.kt
similarity index 60%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceSelectMode.kt
rename to feature/bus/src/main/java/in/koreatech/bus/type/PlaceSelectMode.kt
index 84865ecdc..285820212 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/search/type/PlaceSelectMode.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/PlaceSelectMode.kt
@@ -1,4 +1,4 @@
-package `in`.koreatech.bus.screen.search.type
+package `in`.koreatech.bus.type
enum class PlaceSelectMode {
DEPARTURE,
diff --git a/feature/bus/src/main/java/in/koreatech/bus/type/PlaceType.kt b/feature/bus/src/main/java/in/koreatech/bus/type/PlaceType.kt
new file mode 100644
index 000000000..3e5898b53
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/PlaceType.kt
@@ -0,0 +1,14 @@
+package `in`.koreatech.bus.type
+
+import androidx.annotation.StringRes
+import `in`.koreatech.koin.feature.bus.R
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class PlaceType(
+ @StringRes val titleRes: Int
+) {
+ KOREATECH(R.string.koreatech),
+ STATION(R.string.cheonan_station),
+ TERMINAL(R.string.cheonan_terminal);
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/ShuttleBusRouteType.kt b/feature/bus/src/main/java/in/koreatech/bus/type/ShuttleBusOperationType.kt
similarity index 86%
rename from feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/ShuttleBusRouteType.kt
rename to feature/bus/src/main/java/in/koreatech/bus/type/ShuttleBusOperationType.kt
index 3bd51024b..23d2994bb 100644
--- a/feature/bus/src/main/java/in/koreatech/bus/screen/timetable/type/ShuttleBusRouteType.kt
+++ b/feature/bus/src/main/java/in/koreatech/bus/type/ShuttleBusOperationType.kt
@@ -1,4 +1,4 @@
-package `in`.koreatech.bus.screen.timetable.type
+package `in`.koreatech.bus.type
import android.os.Parcelable
import androidx.annotation.StringRes
@@ -6,7 +6,7 @@ import `in`.koreatech.koin.feature.bus.R
import kotlinx.parcelize.Parcelize
@Parcelize
-enum class ShuttleBusRouteType(
+enum class ShuttleBusOperationType(
@StringRes val titleRes: Int,
@StringRes val simpleTitleRes: Int
) : Parcelable {
diff --git a/feature/bus/src/main/java/in/koreatech/bus/util/CompositionLocal.kt b/feature/bus/src/main/java/in/koreatech/bus/util/CompositionLocal.kt
new file mode 100644
index 000000000..17f88e12d
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/util/CompositionLocal.kt
@@ -0,0 +1,13 @@
+package `in`.koreatech.bus.util
+
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.staticCompositionLocalOf
+import `in`.koreatech.bus.type.BusType
+
+internal val LocalOnRefresh = staticCompositionLocalOf {
+ {}
+}
+
+internal val LocalSelectedTimetableTab = compositionLocalOf {
+ BusType.SHUTTLE
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/util/Constants.kt b/feature/bus/src/main/java/in/koreatech/bus/util/Constants.kt
new file mode 100644
index 000000000..4d940838b
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/util/Constants.kt
@@ -0,0 +1,5 @@
+package `in`.koreatech.bus.util
+
+internal const val CIRCULATION = "순환"
+internal const val WEEKDAY = "주중"
+internal const val WEEKEND = "주말"
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/util/ContextExtensions.kt b/feature/bus/src/main/java/in/koreatech/bus/util/ContextExtensions.kt
new file mode 100644
index 000000000..a44b6504d
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/util/ContextExtensions.kt
@@ -0,0 +1,20 @@
+package `in`.koreatech.bus.util
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.ComponentActivity
+
+internal fun Context.goToArticle(id: Int) {
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ data = Uri.parse("koin://article/activity?fragment=article_detail&article_id=$id&board_id=4")
+ }
+ startActivity(intent)
+}
+
+internal fun Context.findActivity(): ComponentActivity? = when (this) {
+ is ComponentActivity -> this
+ is ContextWrapper -> baseContext.findActivity()
+ else -> null
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/util/DateFormatter.kt b/feature/bus/src/main/java/in/koreatech/bus/util/DateFormatter.kt
deleted file mode 100644
index 0641d60f3..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/util/DateFormatter.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package `in`.koreatech.bus.util
-
-import java.time.LocalDate
-import java.time.format.DateTimeFormatter
-import java.util.Locale
-
-/**
- * 버스 조회 결과 뷰에서 쓰이는 날짜 포맷 String.
- *
- * 오늘, 내일 그리고 이후부터는 ex) 11월 27일(수)
- */
-internal fun LocalDate.formatDateValue(): String {
- if (LocalDate.now() == this)
- return "오늘"
- if (LocalDate.now().plusDays(1) == this)
- return "내일"
-
- return this.format(
- DateTimeFormatter.ofPattern("M월 d일(E)", Locale.KOREA)
- ).replace("요일", "")
-}
-
-/** 버스 조회 결과 뷰에서 쓰이는 출발 시각 포맷 String.
- *
- * ex) 오늘 오전 10:30
- * ex) 11월 27일(수) 오후 3:30
- */
-internal fun formatDepartureTime(
- date: String,
- daytime: String,
- hour: String,
- minute: String
-): String {
- return "$date $daytime ${hour}:${minute.padStart(2, '0')}"
-}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/util/DateTimeFormatter.kt b/feature/bus/src/main/java/in/koreatech/bus/util/DateTimeFormatter.kt
new file mode 100644
index 000000000..1e2520457
--- /dev/null
+++ b/feature/bus/src/main/java/in/koreatech/bus/util/DateTimeFormatter.kt
@@ -0,0 +1,87 @@
+package `in`.koreatech.bus.util
+
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.util.Locale
+
+/**
+ * 버스 조회 결과 뷰에서 쓰이는 날짜 포맷 String.
+ *
+ * 오늘, 내일 그리고 이후부터는 ex) 11월 27일(수)
+ */
+internal fun LocalDate.formatDateValue(): String {
+ if (LocalDate.now() == this)
+ return "오늘"
+ if (LocalDate.now().plusDays(1) == this)
+ return "내일"
+
+ return this.format(
+ DateTimeFormatter.ofPattern("M월 d일(E)", Locale.KOREA)
+ ).replace("요일", "")
+}
+
+/** 버스 조회 결과 뷰에서 쓰이는 출발 시각 포맷 String.
+ *
+ * ex) 오늘 오전 10:30
+ * ex) 11월 27일(수) 오후 3:30
+ */
+internal fun formatDepartureTime(
+ date: String,
+ daytime: String,
+ hour: String,
+ minute: String
+): String {
+ return "$date $daytime ${hour}:${minute.padStart(2, '0')}"
+}
+
+/**
+ * [LocalDateTime]을 yyyy-MM-dd 형식으로 변환.
+ *
+ * ex) 2024-11-11
+ */
+internal fun LocalDateTime.formatUpdatedTime(): String {
+ return this.format(
+ DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ )
+}
+
+/**
+ * [LocalTime]을 a hh:mm 형식으로 변환.
+ *
+ * ex) 오후 10:30
+ */
+internal fun LocalTime.formatTime(): String {
+ return this.format(
+ DateTimeFormatter.ofPattern("a hh:mm")
+ )
+}
+
+/**
+ * [LocalTime] 비교하여 지난 시간을 표시.
+ *
+ * ex) 14분 전
+ * ex) 2시간 33분 전
+ */
+internal fun LocalTime.formatBeforeTime(compareTo: LocalTime): String {
+ val (hour, minute) = this.minusHours(compareTo.hour.toLong()).minusMinutes(compareTo.minute.toLong()).let {
+ it.hour to it.minute
+ }
+
+ return if (hour == 0)
+ "${minute}분 전"
+ else
+ "${hour}시간 ${minute.coerceAtLeast(0)}분 전"
+}
+
+/**
+ * [LocalDate]를 안내 기간으로 변환.
+ *
+ * ex) 2024년 9월 2일
+ */
+internal fun LocalDate.formatPeriod(): String {
+ return this.format(
+ DateTimeFormatter.ofPattern("yyyy년 M월 d일")
+ )
+}
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/viewstate/BusDepartureInfoViewState.kt b/feature/bus/src/main/java/in/koreatech/bus/viewstate/BusDepartureInfoViewState.kt
deleted file mode 100644
index c3f5e5ee0..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/viewstate/BusDepartureInfoViewState.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package `in`.koreatech.bus.viewstate
-
-import `in`.koreatech.bus.screen.timetable.type.BusType
-
-// TODO : 임시 데이터 타입
-data class BusDepartureInfoViewState(
- val type: BusType,
- val departureHour: Int,
- val departureMinute: Int,
- val remainingTime: Int, // 남은 시간(실제 현재 시간 기준)
-)
diff --git a/feature/bus/src/main/java/in/koreatech/bus/viewstate/CommonTimetableViewState.kt b/feature/bus/src/main/java/in/koreatech/bus/viewstate/CommonTimetableViewState.kt
deleted file mode 100644
index 8051bfffc..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/viewstate/CommonTimetableViewState.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package `in`.koreatech.bus.viewstate
-
-import androidx.compose.runtime.Immutable
-import `in`.koreatech.bus.screen.timetable.type.DaytimeType
-
-@Immutable
-data class CommonTimetableViewState(
- val arrivals: Map>,
- val updatedAt: String
-)
-
-data class ArrivalViewState(
- val arrivalTime: String,
-)
-
-// TODO : 임시 데이터, API 아직 없음
\ No newline at end of file
diff --git a/feature/bus/src/main/java/in/koreatech/bus/viewstate/ShuttleTimetableOverviewViewState.kt b/feature/bus/src/main/java/in/koreatech/bus/viewstate/ShuttleTimetableOverviewViewState.kt
deleted file mode 100644
index e36b39fe0..000000000
--- a/feature/bus/src/main/java/in/koreatech/bus/viewstate/ShuttleTimetableOverviewViewState.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package `in`.koreatech.bus.viewstate
-
-import androidx.compose.runtime.Immutable
-import `in`.koreatech.bus.screen.timetable.type.ShuttleBusRouteType
-
-@Immutable
-data class ShuttleTimetableOverviewViewState(
- val routeType: ShuttleBusRouteType,
- val name: String,
- val description: String = "",
-)
-
-@Immutable
-data class ShuttleRegionViewState(
- val name: String,
- val timetableOverviews: List
-)
-
-// TODO : 임시 데이터, API 아직 없음
\ No newline at end of file
diff --git a/feature/bus/src/main/res/drawable/ic_bus_station.xml b/feature/bus/src/main/res/drawable/ic_bus_station.xml
new file mode 100644
index 000000000..0715228ab
--- /dev/null
+++ b/feature/bus/src/main/res/drawable/ic_bus_station.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/feature/bus/src/main/res/drawable/ic_empty_bus.xml b/feature/bus/src/main/res/drawable/ic_empty_bus.xml
new file mode 100644
index 000000000..2104df583
--- /dev/null
+++ b/feature/bus/src/main/res/drawable/ic_empty_bus.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/feature/bus/src/main/res/drawable/ic_fail_whale.xml b/feature/bus/src/main/res/drawable/ic_fail_whale.xml
new file mode 100644
index 000000000..6fd013372
--- /dev/null
+++ b/feature/bus/src/main/res/drawable/ic_fail_whale.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/feature/bus/src/main/res/values/strings.xml b/feature/bus/src/main/res/values/strings.xml
index cc1d29222..bfcbea8ea 100644
--- a/feature/bus/src/main/res/values/strings.xml
+++ b/feature/bus/src/main/res/values/strings.xml
@@ -1,10 +1,16 @@
+
+ %s 시간표
버스 시간표
셔틀버스 시간표
대성버스 시간표
시내버스 시간표
정보가 정확하지 않나요?
+ 천안 터미널 승차
+ 코리아텍 승차
+ %s 승차
+ 전체 차종
셔틀
대성
시내
@@ -21,11 +27,14 @@
오후
업데이트 날짜 : %s
400번
+ 402번
405번
- 495번
- 기점
노선
운행
+ 승하차장명
+ %s(%s ~ %s)의 시간표가 제공됩니다.
+ 등교
+ 하교
교통편 조회하기
@@ -51,6 +60,7 @@
완료
오늘
%s → %s
+ 버스 종류 선택하기
버스 시간표
@@ -60,4 +70,11 @@
버스
유니버스 바로가기
셔틀 승차권
+
+
+ 오류
+ 화면을 불러오지 못했어요
+ 다시 시도하기
+ 결과 없음
+ 검색 결과가 없어요
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f4f7222cd..36f74c4c6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -70,6 +70,7 @@ kotlinxCoroutinesTestVersion = "1.9.0"
turbineVersion = "1.2.0"
kotlinxCollectionsImmutableVersion = "0.3.8"
kspVersion = "1.9.22-1.0.16"
+firebaseCommonKtxVersion = "21.0.0"
[libraries]
@@ -194,6 +195,7 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber"}
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTestVersion" }
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbineVersion" }
+firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtxVersion" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradleVersion" }
diff --git a/koin/build.gradle.kts b/koin/build.gradle.kts
index 07b03d2fd..f8b24149d 100644
--- a/koin/build.gradle.kts
+++ b/koin/build.gradle.kts
@@ -94,6 +94,7 @@ dependencies {
implementation(project(":core:notification"))
implementation(project(":core:navigation"))
implementation(project(":core:designsystem"))
+ implementation(project(":core:analytics"))
implementation(project(":data"))
implementation(project(":domain"))
implementation(project(":core:onboarding"))
diff --git a/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusInteractor.java b/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusInteractor.java
deleted file mode 100644
index 797acef62..000000000
--- a/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusInteractor.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package in.koreatech.koin.data.network.interactor;
-
-import in.koreatech.koin.core.network.ApiCallback;
-import io.reactivex.disposables.Disposable;
-
-
-public interface BusInteractor {
- //시내 버스 현황 가져오기
- Disposable readCityBusList(final ApiCallback apiCallback, String depart, String arrival);
- Disposable readDaesungBusList(final ApiCallback apiCallback, String depart, String arrival);
- Disposable readShuttleBusList(final ApiCallback apiCallback, String depart, String arrival);
-}
-
-
diff --git a/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusRestInteractor.java b/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusRestInteractor.java
deleted file mode 100644
index 429037750..000000000
--- a/koin/src/main/java/in/koreatech/koin/data/network/interactor/BusRestInteractor.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package in.koreatech.koin.data.network.interactor;
-
-import android.util.Log;
-
-import in.koreatech.koin.core.network.ApiCallback;
-import in.koreatech.koin.core.network.RetrofitManager;
-import in.koreatech.koin.data.network.service.BusService;
-import in.koreatech.koin.data.response.bus.BusResponse;
-import io.reactivex.Observer;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.functions.Action;
-import io.reactivex.schedulers.Schedulers;
-import retrofit2.HttpException;
-
-public class BusRestInteractor implements BusInteractor {
- private final String TAG = "CityBusRestInteractor";
-
- public BusRestInteractor() {
- }
-
- @Override
- public Disposable readCityBusList(ApiCallback apiCallback, String depart, String arrival) {
- return RetrofitManager.getInstance().getRetrofit().create(BusService.class).getBusList("city", depart, arrival)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- busResponse -> {
- if (busResponse != null) {
- apiCallback.onSuccess(busResponse);
- } else {
- apiCallback.onFailure(new Throwable("fail read city bus list"));
- }
- },
- throwable -> {
- if (throwable instanceof HttpException) {
- Log.d(TAG, ((HttpException) throwable).code() + " ");
- }
- apiCallback.onFailure(throwable);
- }
- );
- }
-
- @Override
- public Disposable readDaesungBusList(ApiCallback apiCallback, String depart, String arrival) {
- return RetrofitManager.getInstance().getRetrofit().create(BusService.class).getBusList("express", depart, arrival)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- busResponse -> {
- if (busResponse != null) {
- apiCallback.onSuccess(busResponse);
- } else {
- apiCallback.onFailure(new Throwable("fail read express bus list"));
- }
- },
- throwable -> {
- if (throwable instanceof HttpException) {
- Log.d(TAG, ((HttpException) throwable).code() + " ");
- }
- apiCallback.onFailure(throwable);
- }
- );
- }
-
- @Override
- public Disposable readShuttleBusList(ApiCallback apiCallback, String depart, String arrival) {
- return RetrofitManager.getInstance().getRetrofit().create(BusService.class).getBusList("shuttle", depart, arrival)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- busResponse -> {
- if (busResponse != null) {
- apiCallback.onSuccess(busResponse);
- } else {
- apiCallback.onFailure(new Throwable("fail read shuttle bus list"));
- }
- },
- throwable -> {
- if (throwable instanceof HttpException) {
- Log.d(TAG, ((HttpException) throwable).code() + " ");
- }
- apiCallback.onFailure(throwable);
- }
- );
- }
-
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/data/network/service/BusService.java b/koin/src/main/java/in/koreatech/koin/data/network/service/BusService.java
deleted file mode 100644
index 56e3f2005..000000000
--- a/koin/src/main/java/in/koreatech/koin/data/network/service/BusService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package in.koreatech.koin.data.network.service;
-
-import in.koreatech.koin.data.response.bus.BusResponse;
-import io.reactivex.Observable;
-import retrofit2.http.GET;
-import retrofit2.http.Query;
-
-import static in.koreatech.koin.constant.URLConstant.BUS;
-
-
-public interface BusService {
- @GET(BUS)
- Observable getBusList(@Query("bus_type") String busType, @Query("depart") String depart, @Query("arrival") String arrival);
-}
diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt
index e61474230..1fb263c4e 100644
--- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt
+++ b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleActivity.kt
@@ -14,7 +14,7 @@ import `in`.koreatech.koin.core.activity.ActivityBase
import `in`.koreatech.koin.core.analytics.EventAction
import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.appbar.ToolbarMenu
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
+import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.core.navigation.utils.EXTRA_ID
import `in`.koreatech.koin.core.util.dataBinding
import `in`.koreatech.koin.databinding.ActivityArticleBinding
diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt
index 0112b3e64..5d71399a7 100644
--- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt
+++ b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleDetailFragment.kt
@@ -18,19 +18,17 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.BuildConfig
import `in`.koreatech.koin.R
import `in`.koreatech.koin.core.activity.WebViewActivity
import `in`.koreatech.koin.core.analytics.EventAction
import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
+import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.core.dialog.ImageZoomableDialog
import `in`.koreatech.koin.core.download.FileDownloadManager
import `in`.koreatech.koin.core.progressdialog.IProgressDialog
import `in`.koreatech.koin.core.toast.ToastUtil
import `in`.koreatech.koin.core.webview.loadKoreatechHtml
import `in`.koreatech.koin.core.webview.setOnImageClickListener
-import `in`.koreatech.koin.data.constant.URLConstant
import `in`.koreatech.koin.databinding.FragmentArticleDetailBinding
import `in`.koreatech.koin.domain.util.DateFormatUtil
import `in`.koreatech.koin.domain.util.TimeUtil
@@ -43,7 +41,6 @@ import `in`.koreatech.koin.ui.article.viewmodel.ArticleDetailViewModel
import `in`.koreatech.koin.util.ext.withLoading
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
-import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt
index bcaaf9b12..8f55b436c 100644
--- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt
+++ b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleKeywordFragment.kt
@@ -21,7 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint
import `in`.koreatech.koin.R
import `in`.koreatech.koin.core.analytics.EventAction
import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
+import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.core.dialog.AlertModalDialog
import `in`.koreatech.koin.core.dialog.AlertModalDialogData
import `in`.koreatech.koin.core.permission.checkNotificationPermission
diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt
index b74bbfb96..721e852b2 100644
--- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt
+++ b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleListFragment.kt
@@ -25,7 +25,7 @@ import `in`.koreatech.koin.R
import `in`.koreatech.koin.core.analytics.EventAction
import `in`.koreatech.koin.core.analytics.EventLogger
import `in`.koreatech.koin.core.analytics.EventUtils
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
+import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.core.onboarding.ArrowDirection
import `in`.koreatech.koin.core.onboarding.OnboardingManager
import `in`.koreatech.koin.core.onboarding.OnboardingType
diff --git a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt
index 3f7914244..4becded2e 100644
--- a/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt
+++ b/koin/src/main/java/in/koreatech/koin/ui/article/ArticleSearchFragment.kt
@@ -20,7 +20,7 @@ import dagger.hilt.android.AndroidEntryPoint
import `in`.koreatech.koin.R
import `in`.koreatech.koin.core.analytics.EventAction
import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
+import `in`.koreatech.koin.core.analytics.AnalyticsConstant
import `in`.koreatech.koin.core.progressdialog.IProgressDialog
import `in`.koreatech.koin.databinding.FragmentArticleSearchBinding
import `in`.koreatech.koin.ui.article.ArticleDetailFragment.Companion.ARTICLE_ID
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/BusActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/BusActivity.kt
deleted file mode 100644
index bd6704d2a..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/BusActivity.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-package `in`.koreatech.koin.ui.bus
-
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.tabs.TabLayout
-import com.google.android.material.tabs.TabLayoutMediator
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.appbar.AppBarBase
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.core.util.FontManager
-import `in`.koreatech.koin.core.util.dataBinding
-import `in`.koreatech.koin.databinding.BusActivityMainBinding
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.ui.bus.adpater.timetable.pager.BusMainViewPager2Adapter
-import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity
-import `in`.koreatech.koin.ui.navigation.state.MenuState
-import `in`.koreatech.koin.util.FirebasePerformanceUtil
-import `in`.koreatech.koin.util.ext.hideSoftKeyboard
-
-@AndroidEntryPoint
-class BusActivity : KoinNavigationDrawerActivity() {
- private val binding by dataBinding(R.layout.bus_activity_main)
- override val screenTitle = "버스"
- private lateinit var busMainViewPager2Adapter: BusMainViewPager2Adapter
- private val firebasePerformanceUtil by lazy {
- FirebasePerformanceUtil("Bus_Activity")
- }
- override val menuState: MenuState = MenuState.Bus
-
- private val busTabSelectedListener = object : TabLayout.OnTabSelectedListener {
- override fun onTabSelected(tab: TabLayout.Tab?) {
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TAB_MENU,
- tab?.text.toString()
- )
- }
-
- override fun onTabUnselected(tab: TabLayout.Tab?) {
- }
-
- override fun onTabReselected(tab: TabLayout.Tab?) {
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- firebasePerformanceUtil.start()
-
- initView()
- }
-
- private fun initView() = with(binding) {
- val tabStartPosition = intent.getIntExtra("tab", 0)
- val timetableMenu = intent.getStringExtra("timetableMenu") ?: BusType.Shuttle.busTypeString
-
- busMainViewPager2Adapter = BusMainViewPager2Adapter(this@BusActivity, timetableMenu)
-
- koinBaseAppbar.setOnClickListener {
- when (it.id) {
- AppBarBase.getLeftButtonId() -> callDrawerItem(R.id.navi_item_home)
- AppBarBase.getRightButtonId() -> toggleNavigationDrawer()
- }
- }
- busMainViewpager.apply {
- offscreenPageLimit = 3
- adapter = busMainViewPager2Adapter
- }
- TabLayoutMediator(busMainTabs, busMainViewpager) { tab, position ->
- tab.text = when (position) {
- 0 -> getString(R.string.bus_tab_info)
- 1 -> getString(R.string.bus_tab_search_info)
- 2 -> getString(R.string.bus_tab_timetable)
- else -> throw IllegalArgumentException("Position must be lower than ${busMainViewPager2Adapter.itemCount}")
- }
- }.attach()
- if (tabStartPosition != 0) {
- busMainTabs.getTabAt(tabStartPosition)?.select()
- busMainViewpager.currentItem = tabStartPosition
- }
- changeFont(busMainTabs.getChildAt(0))
- busMainTabs.addOnTabSelectedListener(busTabSelectedListener)
- }
-
- override fun onStart() {
- super.onStart()
- binding.busMainViewpager.registerOnPageChangeCallback(object :
- ViewPager2.OnPageChangeCallback() {
- override fun onPageScrollStateChanged(state: Int) {
- super.onPageScrollStateChanged(state)
- hideSoftKeyboard()
- }
- })
- }
-
- private fun changeFont(view: View) {
- val viewGroup = view as ViewGroup
- val childCount = viewGroup.childCount
- for (i in 0 until childCount) {
- val viewChild = viewGroup.getChildAt(i)
- if (viewChild is TextView) {
- viewChild.typeface =
- FontManager.getTypeface(this, FontManager.KoinFontType.PRETENDARD_REGULAR)
- } else (viewChild as? ViewGroup)?.let { changeFont(it) }
- }
- }
-
- override fun onDestroy() {
- firebasePerformanceUtil.stop()
- binding.busMainTabs.removeOnTabSelectedListener(busTabSelectedListener)
- super.onDestroy()
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/BusWidget.java b/koin/src/main/java/in/koreatech/koin/ui/bus/BusWidget.java
deleted file mode 100644
index c07f0cfc7..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/BusWidget.java
+++ /dev/null
@@ -1,363 +0,0 @@
-package in.koreatech.koin.ui.bus;
-
-import static android.app.PendingIntent.FLAG_IMMUTABLE;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.util.Log;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import java.text.ParseException;
-
-import in.koreatech.koin.R;
-import in.koreatech.koin.data.network.entity.RegularSemesterBus;
-import in.koreatech.koin.data.network.entity.SeasonalSemesterBus;
-import in.koreatech.koin.data.network.entity.Term;
-import in.koreatech.koin.data.network.entity.VacationBus;
-import in.koreatech.koin.data.network.interactor.TermInteractor;
-import in.koreatech.koin.data.network.interactor.TermRestInteractor;
-import in.koreatech.koin.data.sharedpreference.BusWidgetSharedPreferencesHelper;
-import in.koreatech.koin.core.network.ApiCallback;
-import in.koreatech.koin.data.network.entity.Bus;
-import in.koreatech.koin.data.network.interactor.BusInteractor;
-import in.koreatech.koin.data.network.interactor.BusRestInteractor;
-import in.koreatech.koin.data.network.response.BusResponse;
-import in.koreatech.koin.constant.BusDestinationEnum;
-
-/**
- * 버스 위젯
- */
-public class BusWidget extends AppWidgetProvider {
- public static final String TAG = "BusWidget";
- private BusInteractor busInteractor;
- private TermInteractor termInteractor;
- private Pair currentBusPair;
- private Context context;
- private static final String SYNC_CLICKED = "buttonTimeTextRefreshClicked";
- private static final String CITYBUS_CLICKED = "buttonKoreatechClicked";
- private static final String EXPRESSEBUS_CLIKCED = "buttonExpressedClicked";
- private static final String SHUTTLE_BUS_CLICKED = "buttonShuttleClicked";
- private static final String REVERSE_BUTTON_CLICKED = "reverseButtonClicked";
- private static final String LEFT_BUTTON_CLICKED = "leftButtonClicked";
- private static final String RIGHT_BUTTON_CLICKED = "rightButtonClicked";
-
- static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId) {
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.bus_widget);
- views.setOnClickPendingIntent(R.id.time_text_refresh_button, getPendingSelfIntent(context, appWidgetId, SYNC_CLICKED));
- views.setOnClickPendingIntent(R.id.city_bus_button, getPendingSelfIntent(context, appWidgetId, CITYBUS_CLICKED));
- views.setOnClickPendingIntent(R.id.koreatech_bus_button, getPendingSelfIntent(context, appWidgetId, SHUTTLE_BUS_CLICKED));
- views.setOnClickPendingIntent(R.id.express_bus_button, getPendingSelfIntent(context, appWidgetId, EXPRESSEBUS_CLIKCED));
- views.setOnClickPendingIntent(R.id.reverse_button, getPendingSelfIntent(context, appWidgetId, REVERSE_BUTTON_CLICKED));
- views.setOnClickPendingIntent(R.id.left_arrow_imagebutton, getPendingSelfIntent(context, appWidgetId, LEFT_BUTTON_CLICKED));
- views.setOnClickPendingIntent(R.id.right_arrow_imagebutton, getPendingSelfIntent(context, appWidgetId, RIGHT_BUTTON_CLICKED));
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
-
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- this.context = context;
- for (int appWidgetId : appWidgetIds) {
- init(context);
- updateAppWidget(context, appWidgetManager, appWidgetId);
- }
-
- }
-
- public void init(Context context) {
- if (context == null) return;
- this.context = context;
- initBusWidgetSharedPreferenece(context);
- int busType = BusWidgetSharedPreferencesHelper.getInstance().loadLastButtonSelectedPosition();
- int destinationType = BusWidgetSharedPreferencesHelper.getInstance().loadBusDestinationType();
- boolean isReversed = BusWidgetSharedPreferencesHelper.getInstance().loadBusSelectionReversed();
- updateBusTime(busType, destinationType, isReversed);
- }
-
- /**
- * 버튼 클릭 및 이벤트를 받아와서 처리
- *
- * @param context
- * @param intent
- */
- @Override
- public void onReceive(Context context, Intent intent) {
- initBusWidgetSharedPreferenece(context);
- int destinationType = BusWidgetSharedPreferencesHelper.getInstance().loadBusDestinationType();
- if (intent.getAction() == null) return;
- Log.e(TAG, "onReceive: " + intent.getAction());
- switch (intent.getAction()) {
- case CITYBUS_CLICKED:
- BusWidgetSharedPreferencesHelper.getInstance().saveLastButtonSelectedPosition(2);
- break;
- case EXPRESSEBUS_CLIKCED:
- BusWidgetSharedPreferencesHelper.getInstance().saveLastButtonSelectedPosition(1);
- break;
- case SHUTTLE_BUS_CLICKED:
- BusWidgetSharedPreferencesHelper.getInstance().saveLastButtonSelectedPosition(0);
- break;
- case REVERSE_BUTTON_CLICKED:
- boolean isReversed = BusWidgetSharedPreferencesHelper.getInstance().loadBusSelectionReversed();
- BusWidgetSharedPreferencesHelper.getInstance().saveBusSelectionReversed(!isReversed);
- break;
- case LEFT_BUTTON_CLICKED:
- if (destinationType - 1 < 0) destinationType = 3;
- BusWidgetSharedPreferencesHelper.getInstance().saveBusDestinationType(--destinationType);
- break;
- case RIGHT_BUTTON_CLICKED:
- BusWidgetSharedPreferencesHelper.getInstance().saveBusDestinationType((++destinationType) % 3);
- break;
- default:
- break;
-
- }
- super.onReceive(context, intent);
- init(context);
- }
-
- @Override
- public void onEnabled(Context context) {
- this.context = context;
- init(context);
- }
-
- @Override
- public void onDisabled(Context context) {
- }
-
- /**
- * {@link BusWidgetSharedPreferencesHelper 초기화
- *
- * @param context
- */
- public void initBusWidgetSharedPreferenece(Context context) {
- BusWidgetSharedPreferencesHelper.getInstance().init(context.getApplicationContext());
- busInteractor = new BusRestInteractor();
- termInteractor = new TermRestInteractor();
- }
-
- /**
- * 버스 시간 업데이트
- *
- * @param busType 버스 종류 0 => 셔틀 버스 1 => 대성 고속 2 => 시내버스
- * @param destinationType 도착 타입 {@link BusDestinationEnum} 참고
- * @param isReversed 화살표가 반대인지 확인
- */
- public void updateBusTime(int busType, int destinationType, boolean isReversed) {
- Pair busPair;
- int arrivalTime = 0;
- busPair = BusDestinationEnum.getValueOf(destinationType, isReversed);
- updateWidgetDepartureAndDestination(busPair.first, busPair.second);
- updateButtonColor(busType);
- this.currentBusPair = busPair;
-
- try {
- switch (busType) {
- case 0:
- termInteractor.readTerm(termApiCallback);
- break;
- case 1:
- arrivalTime = (int) Bus.getRemainExpressTimeToLong(busPair.first, busPair.second, true);
- updateWidgetTimeWIthTime(arrivalTime);
- break;
- case 2:
- busInteractor.readCityBusList(updateCityBusApiCallback, busPair.first, busPair.second);
- break;
- }
- } catch (ParseException e) {
- Log.e(TAG, "updateBusTime: ", e);
- }
- }
-
- /**
- * 시내 버스 도착 시간 값 api callback
- */
- private final ApiCallback updateCityBusApiCallback = new ApiCallback() {
- @Override
- public void onSuccess(Object object) {
- BusResponse busResponse = (BusResponse) object;
- int arrivalTime = busResponse.remainTime / 1000;
- Log.d(TAG, "onSuccess: " + arrivalTime);
- if (arrivalTime == 0) arrivalTime = -1;
- updateWidgetTimeWIthTime(arrivalTime);
- }
-
- @Override
- public void onFailure(Throwable throwable) {
- Log.d(TAG, throwable.getMessage());
- updateWidgetTimeWIthTime(-1);
- }
- };
-
- /**
- * 학기 정보 api callback
- */
- private final ApiCallback termApiCallback = new ApiCallback() { //방학인지 학기중인지 정보를 받아오는 api callback
- @Override
- public void onSuccess(Object object) {
- Term termInfo = (Term) object;
- Bus currentSemesterBus;
- int arrivalTime;
- switch (termInfo.getTerm() % 10) {
- case 0:
- currentSemesterBus = new RegularSemesterBus();
- case 1:
- currentSemesterBus = new SeasonalSemesterBus();
- default:
- currentSemesterBus = new VacationBus();
- }
- try {
- arrivalTime = (int) currentSemesterBus.getRemainShuttleTimeToLong(currentBusPair.first, currentBusPair.second, true);
- updateWidgetTimeWIthTime(arrivalTime);
- }
- catch (Exception e){
- updateWidgetTimeWIthTime(-1);
- }
- }
-
- @Override
- public void onFailure(Throwable throwable) {
- Log.d(TAG, throwable.getMessage());
-
- }
- };
- /**
- * 버스 위젯 시간 update
- *
- * @param seconds 초
- */
- public void updateWidgetTimeWIthTime(int seconds) {
- Log.d(TAG, "updateBusTime: " + seconds);
- RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.bus_widget);
- view.setTextViewText(R.id.bus_time_textview, millsToIntervalTimeToString(seconds));
- ComponentName theWidget = new ComponentName(context, BusWidget.class);
- AppWidgetManager manager = AppWidgetManager.getInstance(context);
- manager.updateAppWidget(theWidget, view);
- }
-
- /**
- * 출발지와 도착지 업데이트
- *
- * @param departure 출발지
- * @param destination 도착지
- */
- public void updateWidgetDepartureAndDestination(String departure, String destination) {
- Log.d(TAG, "departure" + departure + " destination: " + destination);
- RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.bus_widget);
- view.setTextViewText(R.id.bus_start_name, translatePlaceEnglishToKorean(departure));
- view.setTextViewText(R.id.bus_end_name, translatePlaceEnglishToKorean(destination));
- ComponentName theWidget = new ComponentName(context, BusWidget.class);
- AppWidgetManager manager = AppWidgetManager.getInstance(context);
- manager.updateAppWidget(theWidget, view);
- }
-
- /**
- * 버튼 색 변경
- *
- * @param type 버튼 타입
- */
- public void updateButtonColor(int type) {
- Log.d(TAG, "busType : " + type);
- int busChangeButtonId = R.id.koreatech_bus_button;
- RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.bus_widget);
- initButtonView(view);
- switch (type) {
- case 0:
- busChangeButtonId = R.id.koreatech_bus_button;
- break;
- case 1:
- busChangeButtonId = R.id.express_bus_button;
- break;
- case 2:
- busChangeButtonId = R.id.city_bus_button;
- break;
-
- }
- view.setInt(busChangeButtonId, "setBackgroundResource", R.drawable.button_rect_line_squash_radius11dp);
- view.setInt(busChangeButtonId, "setTextColor", Color.argb(100, 247, 148, 30));
- ComponentName theWidget = new ComponentName(context, BusWidget.class);
- AppWidgetManager manager = AppWidgetManager.getInstance(context);
- manager.updateAppWidget(theWidget, view);
- }
-
- /**
- * 버튼 초기화
- *
- * @param view
- */
- public void initButtonView(RemoteViews view) {
- view.setInt(R.id.koreatech_bus_button, "setBackgroundColor", Color.WHITE);
- view.setInt(R.id.express_bus_button, "setBackgroundColor", Color.WHITE);
- view.setInt(R.id.city_bus_button, "setBackgroundColor", Color.WHITE);
- view.setInt(R.id.koreatech_bus_button, "setTextColor", Color.BLACK);
- view.setInt(R.id.express_bus_button, "setTextColor", Color.BLACK);
- view.setInt(R.id.city_bus_button, "setTextColor", Color.BLACK);
- }
-
- /**
- * 남은 시간 String으로 변경
- *
- * @param mills 밀리초
- * @return 남은 시간 String 으로 반환
- */
- public String millsToIntervalTimeToString(int mills) {
- if (mills <= -1) return "운행정보없음";
-
- String strTime;
- int hour = mills / 3600;
- int min = (mills / 60) % 60;
- if (hour > 0)
- strTime = String.format("%d시간 %d분 남음", hour, min);
- else {
- if (min > 1)
- strTime = String.format("%d분 남음", min);
- else {
- strTime = String.format("약 %d분 남음", min);
- }
- }
- return strTime;
- }
-
- /**
- * 도착지 및 출발지를 한국어로 변환
- *
- * @param place 위치
- * @return 한국어로 번역된 도착지 와 출발지
- */
- public String translatePlaceEnglishToKorean(String place) {
- String korean = "";
- switch (place) {
- case "koreatech":
- korean = "한기대";
- break;
- case "terminal":
- korean = "터미널";
- break;
- case "station":
- korean = "천안역";
- break;
- }
- return korean;
- }
-
- /**
- * 버튼 클릭 pedingInetent를 반환 해준다.
- *
- * @param context
- * @param appWidgetId
- * @param action 버튼 클릭 action
- * @return
- */
- private static PendingIntent getPendingSelfIntent(Context context, int appWidgetId, String action) {
- Intent intent = new Intent(context, BusWidget.class).setAction(action);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, FLAG_IMMUTABLE);
- return pendingIntent;
- }
-}
-
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/BusRemainTimeAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/BusRemainTimeAdapter.kt
deleted file mode 100644
index d7cef1343..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/BusRemainTimeAdapter.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater
-
-import `in`.koreatech.koin.databinding.ItemCityBusRemainTimeBinding
-import `in`.koreatech.koin.databinding.ItemExpressBusRemainTimeBinding
-import `in`.koreatech.koin.databinding.ItemShuttleBusRemainTimeBinding
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.ui.bus.state.BusRemainTimeUiState
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.core.view.isVisible
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-
-class BusRemainTimeAdapter :
- ListAdapter(diffCallback) {
- override fun getItemViewType(position: Int): Int {
- return when (getItem(position).type) {
- is BusType.Shuttle -> SHUTTLE
- is BusType.Express -> EXPRESS
- is BusType.City -> CITY
- else -> -1
- }
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return when (viewType) {
- SHUTTLE -> ShuttleViewHolder(
- ItemShuttleBusRemainTimeBinding.inflate(
- LayoutInflater.from(
- parent.context
- )
- )
- )
- EXPRESS -> ExpressViewHolder(
- ItemExpressBusRemainTimeBinding.inflate(
- LayoutInflater.from(
- parent.context
- )
- )
- )
- CITY -> CityViewHolder(ItemCityBusRemainTimeBinding.inflate(LayoutInflater.from(parent.context)))
- else -> throw IllegalArgumentException("Wrong view type")
- }
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is ShuttleViewHolder -> holder.bind(getItem(position))
- is ExpressViewHolder -> holder.bind(getItem(position))
- is CityViewHolder -> holder.bind(getItem(position))
- }
- }
-
- private inner class ShuttleViewHolder(
- private val binding: ItemShuttleBusRemainTimeBinding
- ) : RecyclerView.ViewHolder(binding.root) {
- init {
- binding.root.layoutParams = LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT
- )
- }
-
- fun bind(busRemainTimeUiState: BusRemainTimeUiState) {
- if (busRemainTimeUiState.type != BusType.Shuttle) {
- throw IllegalArgumentException("Not shuttle bus ui state")
- }
-
- with(binding) {
- textViewBusDeparture.text = busRemainTimeUiState.departure
- textViewBusArrival.text = busRemainTimeUiState.arrival
-
- textViewNowArrivalTime.text = busRemainTimeUiState.nowBusRemainTime
- textViewNowDepartureTime.text = busRemainTimeUiState.nowBusDepartureTime
- textViewNextArrivalTime.text = busRemainTimeUiState.nextBusRemainTime
- textViewNextDepartureTime.text = busRemainTimeUiState.nextBusDepartureTime
- }
- }
- }
-
- private inner class ExpressViewHolder(
- private val binding: ItemExpressBusRemainTimeBinding
- ) : RecyclerView.ViewHolder(binding.root) {
- init {
- binding.root.layoutParams = LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT
- )
- }
-
- fun bind(busRemainTimeUiState: BusRemainTimeUiState) {
- if (busRemainTimeUiState.type != BusType.Express) {
- throw IllegalArgumentException("Not express bus ui state")
- }
-
- with(binding) {
- textViewBusDeparture.text = busRemainTimeUiState.departure
- textViewBusArrival.text = busRemainTimeUiState.arrival
-
- textViewNowArrivalTime.text = busRemainTimeUiState.nowBusRemainTime
- textViewNowDepartureTime.text = busRemainTimeUiState.nowBusDepartureTime
- textViewNextArrivalTime.text = busRemainTimeUiState.nextBusRemainTime
- textViewNextDepartureTime.text = busRemainTimeUiState.nextBusDepartureTime
-
- textViewBusType.isVisible = false
- }
- }
- }
-
- private inner class CityViewHolder(
- private val binding: ItemCityBusRemainTimeBinding
- ) : RecyclerView.ViewHolder(binding.root) {
- init {
- binding.root.layoutParams = LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT
- )
- }
-
- fun bind(busRemainTimeUiState: BusRemainTimeUiState) {
- if (busRemainTimeUiState.type != BusType.City) {
- throw IllegalArgumentException("Not city bus ui state")
- }
-
- with(binding) {
- textViewBusDeparture.text = busRemainTimeUiState.departure
- textViewBusArrival.text = busRemainTimeUiState.arrival
-
- textViewNowArrivalTime.text = busRemainTimeUiState.nowBusRemainTime
- textViewNowDepartureTime.text = busRemainTimeUiState.nowBusDepartureTime
- textViewNextArrivalTime.text = busRemainTimeUiState.nextBusRemainTime
- textViewNextDepartureTime.text = busRemainTimeUiState.nextBusDepartureTime
-
- if (busRemainTimeUiState.busNumber == null) {
- textViewBusNumber.isVisible = false
- } else {
- textViewBusNumber.isVisible = true
- textViewBusNumber.text = busRemainTimeUiState.busNumber
- }
- }
- }
- }
-
- companion object {
- private const val SHUTTLE = 0
- private const val EXPRESS = 1
- private const val CITY = 2
-
- private val diffCallback = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(
- oldItem: BusRemainTimeUiState,
- newItem: BusRemainTimeUiState
- ): Boolean {
- return oldItem.departure == newItem.departure && oldItem.arrival == newItem.arrival
- }
-
- override fun areContentsTheSame(
- oldItem: BusRemainTimeUiState,
- newItem: BusRemainTimeUiState
- ): Boolean {
- return oldItem == newItem
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/search/BusSearchResultAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/search/BusSearchResultAdapter.kt
deleted file mode 100644
index fbec30aff..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/search/BusSearchResultAdapter.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.search
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.databinding.BusTimetableSearchResultItemBinding
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.ui.bus.state.BusSearchResultItem
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-
-class BusSearchResultAdapter : ListAdapter(
- itemCallback
-) {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- return ViewHolder(
- BusTimetableSearchResultItemBinding.inflate(
- LayoutInflater.from(parent.context),
- parent,
- false
- ).apply {
- root.layoutParams = ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT
- )
- }
- )
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.bind(getItem(position))
- }
-
- inner class ViewHolder(
- private val binding: BusTimetableSearchResultItemBinding
- ) : RecyclerView.ViewHolder(binding.root) {
- fun bind(busSearchResultItem: BusSearchResultItem) {
- with(binding.root.context) {
- when (busSearchResultItem.busType) {
- BusType.City -> {
- binding.textViewSearchResultBusType.apply {
- text = getString(R.string.bus_name_city)
- setTextColor(ContextCompat.getColor(this@with, R.color.green3))
- }
- }
- BusType.Commuting -> {
- binding.textViewSearchResultBusType.apply {
- text = getString(R.string.bus_name_commuting)
- setTextColor(ContextCompat.getColor(this@with, R.color.colorAccent))
- }
- }
- BusType.Express -> {
- binding.textViewSearchResultBusType.apply {
- text = getString(R.string.bus_name_express)
- setTextColor(ContextCompat.getColor(this@with, R.color.gray))
- }
- }
- BusType.Shuttle -> {
- binding.textViewSearchResultBusType.apply {
- text = getString(R.string.bus_name_shuttle)
- setTextColor(ContextCompat.getColor(this@with, R.color.colorAccent))
- }
- }
- }
-
- binding.textViewSearchResultBusTime.text = busSearchResultItem.time
- }
- }
- }
-
- companion object {
- private val itemCallback = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(
- oldItem: BusSearchResultItem,
- newItem: BusSearchResultItem
- ): Boolean {
- return oldItem.busType == newItem.busType
- }
-
- override fun areContentsTheSame(
- oldItem: BusSearchResultItem,
- newItem: BusSearchResultItem
- ): Boolean {
- return oldItem == newItem
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/CityBusTimetableAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/CityBusTimetableAdapter.kt
deleted file mode 100644
index f642870b8..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/CityBusTimetableAdapter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable
-
-import `in`.koreatech.koin.databinding.BusTimetableCityHeaderBinding
-import `in`.koreatech.koin.databinding.BusTimetableCityItemBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableItemViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.CityBusTimetableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.CityBusTimetableItemViewHolder
-import `in`.koreatech.koin.ui.bus.state.CityBusTimetableUiItem
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.recyclerview.widget.DiffUtil.ItemCallback
-import `in`.koreatech.koin.databinding.BusTimetableCityFooterBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableFooterViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.CityBusTimetableFooterViewHolder
-
-class CityBusTimetableAdapter : TableAdapter(itemCallback) {
-
- override fun onCreateHeaderViewHolder(parent: ViewGroup): TableHeaderViewHolder {
- return CityBusTimetableHeaderViewHolder(
- BusTimetableCityHeaderBinding.inflate(
- LayoutInflater.from(parent.context)
- )
- )
- }
-
- override fun onCreateItemViewHolder(parent: ViewGroup): TableItemViewHolder {
- return CityBusTimetableItemViewHolder(
- BusTimetableCityItemBinding.inflate(LayoutInflater.from(parent.context)).apply {
- root.layoutParams = LinearLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT
- )
- }
- )
- }
-
- override fun onCreateFooterViewHolder(parent: ViewGroup): TableFooterViewHolder {
- return CityBusTimetableFooterViewHolder(
- BusTimetableCityFooterBinding.inflate(LayoutInflater.from(parent.context))
- )
- }
-
- companion object {
- private val itemCallback = object : ItemCallback() {
- override fun areItemsTheSame(
- oldItem: CityBusTimetableUiItem,
- newItem: CityBusTimetableUiItem
- ): Boolean {
- return oldItem.am == newItem.am
- }
-
- override fun areContentsTheSame(
- oldItem: CityBusTimetableUiItem,
- newItem: CityBusTimetableUiItem
- ): Boolean {
- return oldItem == newItem
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ExpressBusTimetableAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ExpressBusTimetableAdapter.kt
deleted file mode 100644
index 41012b020..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ExpressBusTimetableAdapter.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable
-
-import `in`.koreatech.koin.databinding.BusTimetableExpressHeaderBinding
-import `in`.koreatech.koin.databinding.BusTimetableExpressItemBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableItemViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ExpressBusTimetableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ExpressBusTimetableItemViewHolder
-import `in`.koreatech.koin.ui.bus.state.ExpressBusTimetableUiItem
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.recyclerview.widget.DiffUtil.ItemCallback
-import `in`.koreatech.koin.databinding.BusTimetableExpressFooterBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableFooterViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ExpressBusTimetableFooterViewHolder
-
-class ExpressBusTimetableAdapter : TableAdapter(itemCallback) {
-
- override fun onCreateHeaderViewHolder(parent: ViewGroup): TableHeaderViewHolder {
- return ExpressBusTimetableHeaderViewHolder(
- BusTimetableExpressHeaderBinding.inflate(
- LayoutInflater.from(parent.context)
- )
- )
- }
-
- override fun onCreateItemViewHolder(parent: ViewGroup): TableItemViewHolder {
- return ExpressBusTimetableItemViewHolder(
- BusTimetableExpressItemBinding.inflate(LayoutInflater.from(parent.context)).apply {
- root.layoutParams = ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT
- )
- }
- )
- }
-
- override fun onCreateFooterViewHolder(parent: ViewGroup): TableFooterViewHolder {
- return ExpressBusTimetableFooterViewHolder(
- BusTimetableExpressFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- )
- }
-
- companion object {
- private val itemCallback = object : ItemCallback() {
- override fun areItemsTheSame(
- oldItem: ExpressBusTimetableUiItem,
- newItem: ExpressBusTimetableUiItem
- ): Boolean {
- return oldItem.arrivalTime == newItem.arrivalTime
- }
-
- override fun areContentsTheSame(
- oldItem: ExpressBusTimetableUiItem,
- newItem: ExpressBusTimetableUiItem
- ): Boolean {
- return oldItem == newItem
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ShuttleBusTimetableAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ShuttleBusTimetableAdapter.kt
deleted file mode 100644
index a39ad63ed..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/ShuttleBusTimetableAdapter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable
-
-import `in`.koreatech.koin.databinding.BusTimetableShuttleHeaderBinding
-import `in`.koreatech.koin.databinding.BusTimetableShuttleItemBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableItemViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ShuttleBusTimetableHeaderViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ShuttleBusTimetableItemViewHolder
-import `in`.koreatech.koin.ui.bus.state.ShuttleBusTimetableUiItem
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.recyclerview.widget.DiffUtil.ItemCallback
-import `in`.koreatech.koin.databinding.BusTimetableShuttleFooterBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.TableFooterViewHolder
-import `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder.ShuttleBusTimetableFooterViewHolder
-
-class ShuttleBusTimetableAdapter : TableAdapter(itemCallback) {
-
- override fun onCreateHeaderViewHolder(parent: ViewGroup): TableHeaderViewHolder {
- return ShuttleBusTimetableHeaderViewHolder(
- BusTimetableShuttleHeaderBinding.inflate(
- LayoutInflater.from(parent.context), parent, false
- )
- )
- }
-
- override fun onCreateItemViewHolder(parent: ViewGroup): TableItemViewHolder {
- return ShuttleBusTimetableItemViewHolder(
- BusTimetableShuttleItemBinding.inflate(LayoutInflater.from(parent.context)).apply {
- root.layoutParams = LinearLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT
- )
- }
- )
- }
-
- override fun onCreateFooterViewHolder(parent: ViewGroup): TableFooterViewHolder {
- return ShuttleBusTimetableFooterViewHolder(
- BusTimetableShuttleFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- )
- }
-
- companion object {
- private val itemCallback = object : ItemCallback() {
- override fun areItemsTheSame(
- oldItem: ShuttleBusTimetableUiItem,
- newItem: ShuttleBusTimetableUiItem
- ): Boolean {
- return oldItem.node == newItem.node
- }
-
- override fun areContentsTheSame(
- oldItem: ShuttleBusTimetableUiItem,
- newItem: ShuttleBusTimetableUiItem
- ): Boolean {
- return oldItem == newItem
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusMainViewPager2Adapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusMainViewPager2Adapter.kt
deleted file mode 100644
index d7e4c6419..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusMainViewPager2Adapter.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.pager
-
-import `in`.koreatech.koin.ui.bus.fragment.BusMainFragment
-import `in`.koreatech.koin.ui.bus.fragment.BusSearchFragment
-import `in`.koreatech.koin.ui.bus.fragment.BusTimetableFragment
-import android.util.SparseArray
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import `in`.koreatech.koin.domain.model.bus.BusType
-
-class BusMainViewPager2Adapter(
- fragmentActivity: FragmentActivity,
- private val type: String
-): FragmentStateAdapter(fragmentActivity) {
-
- private val registeredFragments = SparseArray()
-
- override fun getItemCount() = 3
-
- override fun createFragment(position: Int): Fragment {
- return registeredFragments[position] ?: when(position) {
- 0 -> BusMainFragment()
- 1 -> BusSearchFragment()
- 2 -> BusTimetableFragment.newInstance(type)
- else -> throw IllegalArgumentException("Position must be lower than $itemCount")
- }.also { registeredFragments[position] = it }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusTimetableViewPager2Adapter.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusTimetableViewPager2Adapter.kt
deleted file mode 100644
index 35a5d51df..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/pager/BusTimetableViewPager2Adapter.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.pager
-
-import `in`.koreatech.koin.ui.bus.fragment.*
-import android.util.SparseArray
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import androidx.viewpager2.adapter.FragmentStateAdapter
-
-class BusTimetableViewPager2Adapter(
- fragmentActivity: FragmentActivity
-): FragmentStateAdapter(fragmentActivity) {
-
- private val registeredFragments = SparseArray()
-
- override fun getItemCount() = 3
-
- override fun createFragment(position: Int): Fragment {
- return registeredFragments[position] ?: when(position) {
- 0 -> ShuttleBusTimetableFragment()
- 1 -> ExpressBusTimetableFragment()
- 2 -> CityBusTimetableFragment()
- else -> throw IllegalArgumentException("Position must be lower than $itemCount")
- }.also { registeredFragments[position] = it }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableFooterViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableFooterViewHolder.kt
deleted file mode 100644
index 46b91f9b5..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableFooterViewHolder.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableCityFooterBinding
-
-class CityBusTimetableFooterViewHolder(binding: BusTimetableCityFooterBinding)
- : TableFooterViewHolder(binding)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableHeaderViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableHeaderViewHolder.kt
deleted file mode 100644
index 531f04882..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableHeaderViewHolder.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableCityHeaderBinding
-
-class CityBusTimetableHeaderViewHolder(binding: BusTimetableCityHeaderBinding)
- : TableHeaderViewHolder(binding)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableItemViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableItemViewHolder.kt
deleted file mode 100644
index e42a66c13..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/CityBusTimetableItemViewHolder.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableCityItemBinding
-import `in`.koreatech.koin.ui.bus.state.CityBusTimetableUiItem
-
-class CityBusTimetableItemViewHolder(private val binding: BusTimetableCityItemBinding) :
- TableItemViewHolder(binding) {
- override fun bind(item: CityBusTimetableUiItem) {
- with(binding) {
- textViewBusLocation.text = item.am
- textViewBusRideTime.text = item.pm
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ExpressBusTimetableItemViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ExpressBusTimetableItemViewHolder.kt
deleted file mode 100644
index c35e89cf8..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ExpressBusTimetableItemViewHolder.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableExpressItemBinding
-import `in`.koreatech.koin.ui.bus.state.ExpressBusTimetableUiItem
-
-class ExpressBusTimetableItemViewHolder(private val binding: BusTimetableExpressItemBinding) :
- TableItemViewHolder(binding) {
- override fun bind(item: ExpressBusTimetableUiItem) {
- with(binding) {
- textViewBusArrivalTime.text = item.arrivalTime
- textViewBusDepartureTime.text = item.departureTime
- textViewCharge.text = item.charge
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableFooterViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableFooterViewHolder.kt
deleted file mode 100644
index 891b26d63..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableFooterViewHolder.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableShuttleFooterBinding
-
-class ShuttleBusTimetableFooterViewHolder(binding: BusTimetableShuttleFooterBinding)
- : TableFooterViewHolder(binding)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableHeaderViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableHeaderViewHolder.kt
deleted file mode 100644
index 244651bb1..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableHeaderViewHolder.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableShuttleHeaderBinding
-
-class ShuttleBusTimetableHeaderViewHolder(binding: BusTimetableShuttleHeaderBinding)
- : TableHeaderViewHolder(binding)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableItemViewHolder.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableItemViewHolder.kt
deleted file mode 100644
index fa5b2dc86..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/adpater/timetable/viewholder/ShuttleBusTimetableItemViewHolder.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package `in`.koreatech.koin.ui.bus.adpater.timetable.viewholder
-
-import `in`.koreatech.koin.databinding.BusTimetableShuttleItemBinding
-import `in`.koreatech.koin.ui.bus.state.ShuttleBusTimetableUiItem
-
-class ShuttleBusTimetableItemViewHolder(private val binding: BusTimetableShuttleItemBinding) :
- TableItemViewHolder(binding) {
- override fun bind(item: ShuttleBusTimetableUiItem) {
- with(binding) {
- textViewBusLocation.text = item.node
- textViewBusRideTime.text = item.arrivalTime
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/dialog/BusSearchResultDialog.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/dialog/BusSearchResultDialog.kt
deleted file mode 100644
index e87cdf3f5..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/dialog/BusSearchResultDialog.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package `in`.koreatech.koin.ui.bus.dialog
-
-import `in`.koreatech.koin.databinding.BusTimetableSearchResultDialogBinding
-import `in`.koreatech.koin.ui.bus.adpater.search.BusSearchResultAdapter
-import `in`.koreatech.koin.ui.bus.state.BusSearchResultItem
-import `in`.koreatech.koin.util.ext.windowHeight
-import `in`.koreatech.koin.util.ext.windowWidth
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.fragment.app.DialogFragment
-import androidx.recyclerview.widget.LinearLayoutManager
-
-class BusSearchResultDialog : DialogFragment() {
-
- private var _binding: BusTimetableSearchResultDialogBinding? = null
- val binding get() = _binding!!
-
- private val busSearchResultAdapter = BusSearchResultAdapter()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = BusTimetableSearchResultDialogBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onResume() {
- super.onResume()
- val params: ViewGroup.LayoutParams? = dialog?.window?.attributes
-
- params?.width = (windowWidth * 0.9).toInt()
- dialog?.window?.attributes = params as WindowManager.LayoutParams
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- binding.recyclerView.apply {
- layoutManager = LinearLayoutManager(requireContext())
- adapter = busSearchResultAdapter
- }
- binding.buttonClose.setOnClickListener { dismiss() }
- }
-
- fun submitList(list: List) {
- busSearchResultAdapter.submitList(list)
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt
deleted file mode 100644
index 677f671bd..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.activity.ActivityBase
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.databinding.BusMainFragmentBinding
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.busNodeSelection
-import `in`.koreatech.koin.domain.model.bus.spinnerSelection
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.ui.bus.adpater.BusRemainTimeAdapter
-import `in`.koreatech.koin.ui.bus.state.toCityBusRemainTimeUiState
-import `in`.koreatech.koin.ui.bus.state.toExpressBusRemainTimeUiState
-import `in`.koreatech.koin.ui.bus.state.toShuttleBusRemainTimeUiState
-import `in`.koreatech.koin.ui.bus.viewmodel.BusMainFragmentViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import `in`.koreatech.koin.util.ext.setOnItemSelectedListener
-import `in`.koreatech.koin.util.ext.withLoading
-import `in`.koreatech.koin.util.ext.withToastError
-import android.os.Bundle
-import android.util.Log
-import android.view.View
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.LinearLayoutManager
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.core.util.dataBinding
-
-@AndroidEntryPoint
-class BusMainFragment : Fragment(R.layout.bus_main_fragment) {
- private val binding by dataBinding()
-
- private val viewModel by viewModels()
-
- private val busRemainTimeAdapter = BusRemainTimeAdapter()
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() = with(binding) {
- busDepartureSpinner.setSelection(BusNode.Koreatech.spinnerSelection)
- busArrivalSpinner.setSelection(BusNode.Terminal.spinnerSelection)
- busDepartureSpinner.setOnTouchListener { _, _ ->
- viewModel.isUserSelection = true
- busDepartureSpinner.performClick()
- }
- busDepartureSpinner.setOnItemSelectedListener { _, _, position, _ ->
- viewModel.setDeparture(position.busNodeSelection)
- if(viewModel.isUserSelection) {
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_DEPARTURE,
- resources.getStringArray(R.array.bus_place)[position]
- )
- viewModel.isUserSelection = false
- }
- }
-
- busArrivalSpinner.setOnTouchListener { _, _ ->
- viewModel.isUserSelection = true
- busArrivalSpinner.performClick()
- }
- busArrivalSpinner.setOnItemSelectedListener { _, _, position, _ ->
- viewModel.setArrival(position.busNodeSelection)
- if(viewModel.isUserSelection) {
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_ARRIVAL,
- resources.getStringArray(R.array.bus_place)[position]
- )
- viewModel.isUserSelection = false
- }
- }
- recyclerView.apply {
- layoutManager = LinearLayoutManager(requireContext())
- adapter = busRemainTimeAdapter
- itemAnimator = null
- }
- }
-
- private fun initViewModel() = with(viewModel) {
- (activity as ActivityBase).withLoading(viewLifecycleOwner, this)
- withToastError(this@BusMainFragment, binding.root)
-
- observeLiveData(departure) {
- binding.busDepartureSpinner.setSelection(it.spinnerSelection)
- }
- observeLiveData(arrival) {
- binding.busArrivalSpinner.setSelection(it.spinnerSelection)
- }
-
- observeLiveData(busTimer) { list ->
- val uiState = list.mapNotNull {
- when (it) {
- is BusArrivalInfo.CityBusArrivalInfo -> it.toCityBusRemainTimeUiState(
- requireContext(),
- departure.value ?: BusNode.Koreatech,
- arrival.value ?: BusNode.Terminal
- )
- is BusArrivalInfo.ExpressBusArrivalInfo -> it.toExpressBusRemainTimeUiState(
- requireContext(),
- departure.value ?: BusNode.Koreatech,
- arrival.value ?: BusNode.Terminal
- )
- is BusArrivalInfo.ShuttleBusArrivalInfo -> it.toShuttleBusRemainTimeUiState(
- requireContext(),
- departure.value ?: BusNode.Koreatech,
- arrival.value ?: BusNode.Terminal
- )
- else -> null
- }
- }
- busRemainTimeAdapter.submitList(uiState)
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusSearchFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusSearchFragment.kt
deleted file mode 100644
index 1f37e654d..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusSearchFragment.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.core.progressdialog.IProgressDialog
-import `in`.koreatech.koin.databinding.BusTimetableSearchFragmentBinding
-import `in`.koreatech.koin.domain.model.bus.busNodeSelection
-import `in`.koreatech.koin.domain.model.bus.spinnerSelection
-import `in`.koreatech.koin.ui.bus.dialog.BusSearchResultDialog
-import `in`.koreatech.koin.ui.bus.state.BusSearchResultItem
-import `in`.koreatech.koin.ui.bus.state.toBusSearchResultItem
-import `in`.koreatech.koin.ui.bus.viewmodel.BusSearchViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import `in`.koreatech.koin.util.ext.setOnItemSelectedListener
-import `in`.koreatech.koin.util.ext.withLoading
-import `in`.koreatech.koin.util.ext.withToastError
-import android.app.DatePickerDialog
-import android.app.DatePickerDialog.OnDateSetListener
-import android.os.Bundle
-import android.view.View
-import android.widget.DatePicker
-import androidx.fragment.app.commit
-import androidx.fragment.app.viewModels
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import timber.log.Timber
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
-
-@AndroidEntryPoint
-class BusSearchFragment : DataBindingFragment() {
- override val layoutId: Int = R.layout.bus_timetable_search_fragment
-
- private val busSearchViewModel by viewModels()
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() = with(binding) {
- busSearchBusDepartureSpinner.setOnItemSelectedListener { _, _, position, _ ->
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_SEARCH_DEPARTURE,
- binding.busSearchBusDepartureSpinner.selectedItem.toString()
- )
- busSearchViewModel.setDeparture(position.busNodeSelection)
- }
-
- busSearchBusArrivalSpinner.setOnItemSelectedListener { _, _, position, _ ->
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_SEARCH_ARRIVAL,
- binding.busSearchBusArrivalSpinner.selectedItem.toString()
- )
- busSearchViewModel.setArrival(position.busNodeSelection)
- }
-
- busTimetableSearchDateImageButton.setOnClickListener {
- showDatePickerDialog()
- }
-
- busSearchTimePicker.setOnTimeChangedListener { _, hourOfDay, minute ->
- busSearchViewModel.setSelectedTime(LocalTime.of(hourOfDay, minute))
- }
-
- busTimetableSearchFragmentSearchButton.setOnClickListener {
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_SEARCH,
- "조회"
- )
- busSearchViewModel.search()
- }
- }
-
- private fun initViewModel() = with(busSearchViewModel) {
- (requireActivity() as? IProgressDialog)?.withLoading(viewLifecycleOwner, this)
- withToastError(this@BusSearchFragment, binding.root)
-
- observeLiveData(selectedDate) {
- binding.busTimetableSearchDateTextview.text = it.format(
- DateTimeFormatter.ofPattern("MM월 dd일 (E)")
- )
-
- binding.busTimetableSearchFragmentInformationTextview.text =
- LocalDateTime.of(selectedDate.value, selectedTime.value).format(
- DateTimeFormatter.ofPattern("yyyy / MM / dd a hh시 mm분")
- )
- }
-
- observeLiveData(selectedTime) {
- binding.busTimetableSearchFragmentInformationTextview.text =
- LocalDateTime.of(selectedDate.value, selectedTime.value).format(
- DateTimeFormatter.ofPattern("yyyy / MM / dd a hh시 mm분")
- )
- }
-
- observeLiveData(departure) {
- binding.busSearchBusDepartureSpinner.setSelection(it.spinnerSelection)
- }
-
- observeLiveData(arrival) {
- binding.busSearchBusArrivalSpinner.setSelection(it.spinnerSelection)
- }
-
- observeLiveData(busSearchResult) {
- showSearchResultDialog(it.map { it.toBusSearchResultItem() })
- }
- }
-
- private fun showDatePickerDialog() {
- val (year, month, dayOfMonth) = (busSearchViewModel.selectedDate.value
- ?: LocalDate.now()).let {
- Triple(it.year, it.monthValue - 1, it.dayOfMonth)
- }
- DatePickerDialog(
- requireContext(),
- { _, year, month, dayOfMonth ->
- busSearchViewModel.setSelectedDate(
- LocalDate.of(year, month + 1, dayOfMonth)
- )
- },
- year, month, dayOfMonth
- ).show()
- }
-
- private fun showSearchResultDialog(list: List) {
- with(requireActivity().supportFragmentManager) {
- val prev = findFragmentByTag(SEARCH_RESULT_DIALOG)
- if (prev != null) commit {
- remove(prev)
- }
-
- val busSearchResultDialog = BusSearchResultDialog()
- busSearchResultDialog.show(this, SEARCH_RESULT_DIALOG)
- busSearchResultDialog.submitList(list)
- }
- }
-
- companion object {
- const val SEARCH_RESULT_DIALOG = "SEARCH_RESULT_DIALOG"
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt
deleted file mode 100644
index 56e5db843..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.databinding.BusTimetableFragmentBinding
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.ui.bus.adpater.timetable.pager.BusTimetableViewPager2Adapter
-import `in`.koreatech.koin.ui.bus.viewmodel.BusTimetableViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import android.os.Bundle
-import android.view.View
-import androidx.fragment.app.commit
-import androidx.fragment.app.viewModels
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.domain.model.bus.toBusType
-import `in`.koreatech.koin.ui.main.fragment.DiningContainerFragment
-
-@AndroidEntryPoint
-class BusTimetableFragment : DataBindingFragment() {
- override val layoutId: Int = R.layout.bus_timetable_fragment
-
- private val busTimetableViewModel by viewModels()
-
- private val busTimetableViewPager2Adapter by lazy { BusTimetableViewPager2Adapter(requireActivity()) }
-
- private val busType by lazy { arguments?.getString(TYPE)?.toBusType() ?: BusType.Shuttle }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() {
- busTimetableViewModel.setBusType(busType)
-
- binding.busTimetablePager.apply {
- offscreenPageLimit = 3
- isUserInputEnabled = false
- adapter = busTimetableViewPager2Adapter
- }
-
- binding.busTimetableBustypeShuttle.setOnClickListener {
- busTimetableViewModel.setBusType(BusType.Shuttle)
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE,
- getString(R.string.bus_name_school_shuttle)
- )
- }
- binding.busTimetableBustypeDaesung.setOnClickListener {
- busTimetableViewModel.setBusType(BusType.Express)
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE,
- getString(R.string.bus_name_express)
- )
- }
- binding.busTimetableBustypeCity.setOnClickListener {
- busTimetableViewModel.setBusType(BusType.City)
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE,
- getString(R.string.bus_name_city)
- )
- }
- }
-
- private fun initViewModel() = with(busTimetableViewModel) {
- observeLiveData(showingBusType) {
- when(it) {
- BusType.City -> switchCityBusTimetable()
- BusType.Express -> switchExpressBusTimetable()
- BusType.Shuttle -> switchShuttleBusTimetable()
- else -> Unit
- }
- }
- }
-
- private fun switchShuttleBusTimetable() = with(binding) {
- busTimetablePager.setCurrentItem(0, true)
- busTimetableBustypeShuttle.alpha = 1f
- busTimetableBustypeDaesung.alpha = 0.5f
- busTimetableBustypeCity.alpha = 0.5f
- }
-
- private fun switchExpressBusTimetable() = with(binding) {
- busTimetablePager.setCurrentItem(1, true)
- busTimetableBustypeShuttle.alpha = 0.5f
- busTimetableBustypeDaesung.alpha = 1f
- busTimetableBustypeCity.alpha = 0.5f
- }
-
- private fun switchCityBusTimetable() = with(binding) {
- busTimetablePager.setCurrentItem(2, true)
- busTimetableBustypeShuttle.alpha = 0.5f
- busTimetableBustypeDaesung.alpha = 0.5f
- busTimetableBustypeCity.alpha = 1f
- }
-
- companion object {
- private const val TYPE = "type"
- fun newInstance(type: String) =
- BusTimetableFragment().apply {
- arguments = Bundle().apply {
- putString(TYPE, type)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/CityBusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/CityBusTimetableFragment.kt
deleted file mode 100644
index d850164e5..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/CityBusTimetableFragment.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import android.os.Bundle
-import android.view.View
-import android.widget.ArrayAdapter
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.LinearLayoutManager
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.core.progressdialog.IProgressDialog
-import `in`.koreatech.koin.databinding.LayoutCityBusTimetableBinding
-import `in`.koreatech.koin.domain.model.bus.city.CityBusGeneralDestination
-import `in`.koreatech.koin.domain.model.bus.city.CityBusNumber
-import `in`.koreatech.koin.domain.model.bus.city.toggleSelection
-import `in`.koreatech.koin.ui.bus.adpater.timetable.CityBusTimetableAdapter
-import `in`.koreatech.koin.ui.bus.state.toCityBusTimetableUiItemList
-import `in`.koreatech.koin.ui.bus.viewmodel.CityBusTimetableViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import `in`.koreatech.koin.util.ext.setOnItemSelectedListener
-import `in`.koreatech.koin.util.ext.withLoading
-import `in`.koreatech.koin.util.ext.withToastError
-
-@AndroidEntryPoint
-class CityBusTimetableFragment : DataBindingFragment() {
- override val layoutId: Int = R.layout.layout_city_bus_timetable
-
- private val cityBusTimetableViewModel by viewModels()
- private val cityBusTimetableAdapter = CityBusTimetableAdapter()
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() = with(binding) {
- recyclerView.apply {
- layoutManager = LinearLayoutManager(requireContext())
- adapter = cityBusTimetableAdapter
- itemAnimator = null
- }
- busTimetableCityCourseToggle.setOnCheckedChangeListener { _, isChecked ->
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE_CITYBUS,
- binding.busTimetableCityCourseToggle.text.toString()
- )
- val selection =
- if (isChecked) CityBusGeneralDestination.Terminal.toggleSelection else CityBusGeneralDestination.Beongchon.toggleSelection
- cityBusTimetableViewModel.setDestination(selection)
- }
- busTimetableCityBusNumberSpinner.adapter =
- ArrayAdapter(
- requireContext(),
- android.R.layout.simple_spinner_dropdown_item,
- CityBusNumber.entries.map { "${it.number}번" }
- )
- busTimetableCityBusNumberSpinner.setOnItemSelectedListener { _, _, position, _ ->
- cityBusTimetableViewModel.setBusNumber(position)
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE_CITYBUS_ROUTE,
- busTimetableCityBusNumberSpinner.selectedItem.toString()
- )
- }
- }
-
- private fun initViewModel() = with(cityBusTimetableViewModel) {
- (requireActivity() as? IProgressDialog)?.withLoading(viewLifecycleOwner, this)
-
- withToastError(this@CityBusTimetableFragment, binding.root)
-
- observeLiveData(busDepartTimes) { list ->
- cityBusTimetableAdapter.submitList(list.toCityBusTimetableUiItemList())
- binding.recyclerView.smoothScrollToPosition(0)
- }
-
- observeLiveData(updatedAt) {
- cityBusTimetableAdapter.setUpdatedAt(it)
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt
deleted file mode 100644
index 0cfeada8d..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import android.os.Bundle
-import android.view.View
-import android.widget.ArrayAdapter
-import androidx.core.view.isVisible
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.LinearLayoutManager
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.core.progressdialog.IProgressDialog
-import `in`.koreatech.koin.databinding.LayoutExpressBusTimetableBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.ExpressBusTimetableAdapter
-import `in`.koreatech.koin.ui.bus.state.toExpressBusTimetableUiItem
-import `in`.koreatech.koin.ui.bus.viewmodel.ExpressBusTimetableViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import `in`.koreatech.koin.util.ext.setOnItemSelectedListener
-import `in`.koreatech.koin.util.ext.withLoading
-import `in`.koreatech.koin.util.ext.withToastError
-
-@AndroidEntryPoint
-class ExpressBusTimetableFragment : DataBindingFragment() {
- override val layoutId: Int = R.layout.layout_express_bus_timetable
-
- private val expressBusTimetableViewModel by viewModels()
- private val expressBusTimetableAdapter = ExpressBusTimetableAdapter()
- private var isInitialization = true
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() = with(binding) {
- recyclerView.apply {
- layoutManager = LinearLayoutManager(requireContext())
- adapter = expressBusTimetableAdapter
- itemAnimator = null
- }
-
- busTimetableCoursesSpinner.setOnItemSelectedListener { _, _, position, _ ->
- expressBusTimetableViewModel.setCoursePosition(position)
- if (isInitialization) {
- isInitialization = false
- return@setOnItemSelectedListener
- }
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE_EXPRESS,
- busTimetableCoursesSpinner.selectedItem.toString()
- )
- }
- }
-
- private fun initViewModel() = with(expressBusTimetableViewModel) {
- (requireActivity() as? IProgressDialog)?.withLoading(viewLifecycleOwner, this)
-
- withToastError(this@ExpressBusTimetableFragment, binding.root)
-
- observeLiveData(busCoursesString) { courses ->
- if (courses.isNullOrEmpty()) {
- binding.busTimetableCoursesSpinner.isVisible = false
- } else {
- binding.busTimetableCoursesSpinner.isVisible = true
- binding.busTimetableCoursesSpinner.adapter =
- ArrayAdapter(
- requireContext(),
- android.R.layout.simple_spinner_dropdown_item,
- courses
- )
- }
- }
-
- observeLiveData(selectedCoursesPosition) {
- binding.busTimetableCoursesSpinner.setSelection(it, true)
- }
-
- observeLiveData(busTimetables) { list ->
- expressBusTimetableAdapter.submitList(
- list.map { it.toExpressBusTimetableUiItem(requireContext()) }
- )
- binding.recyclerView.smoothScrollToPosition(0)
- }
-
- observeLiveData(updatedAt) {
- expressBusTimetableAdapter.setUpdatedAt(it)
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt
deleted file mode 100644
index 2ab864797..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-package `in`.koreatech.koin.ui.bus.fragment
-
-import android.os.Bundle
-import android.view.View
-import android.widget.ArrayAdapter
-import androidx.core.view.isVisible
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.LinearLayoutManager
-import dagger.hilt.android.AndroidEntryPoint
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.core.analytics.EventAction
-import `in`.koreatech.koin.core.analytics.EventLogger
-import `in`.koreatech.koin.core.constant.AnalyticsConstant
-import `in`.koreatech.koin.core.fragment.DataBindingFragment
-import `in`.koreatech.koin.core.progressdialog.IProgressDialog
-import `in`.koreatech.koin.databinding.LayoutShuttleBusTimetableBinding
-import `in`.koreatech.koin.ui.bus.adpater.timetable.ShuttleBusTimetableAdapter
-import `in`.koreatech.koin.ui.bus.state.toShuttleBusTimetableUiItem
-import `in`.koreatech.koin.ui.bus.viewmodel.ShuttleBusTimetableViewModel
-import `in`.koreatech.koin.util.ext.observeLiveData
-import `in`.koreatech.koin.util.ext.setOnItemSelectedListener
-import `in`.koreatech.koin.util.ext.withLoading
-import `in`.koreatech.koin.util.ext.withToastError
-
-@AndroidEntryPoint
-class ShuttleBusTimetableFragment : DataBindingFragment() {
- override val layoutId: Int = R.layout.layout_shuttle_bus_timetable
-
- private val shuttleBusTimetableViewModel by viewModels()
- private val shuttleBusTimetableAdapter = ShuttleBusTimetableAdapter()
- private var isCourseInitialization = true
- private var isRouteInitialization = true
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- initView()
- initViewModel()
- }
-
- private fun initView() = with(binding) {
- recyclerView.apply {
- layoutManager = LinearLayoutManager(requireContext())
- adapter = shuttleBusTimetableAdapter
- itemAnimator = null
- }
-
- busTimetableCoursesSpinner.setOnItemSelectedListener { _, _, position, _ ->
- shuttleBusTimetableViewModel.setCoursePosition(position)
- if (isCourseInitialization) {
- isCourseInitialization = false
- return@setOnItemSelectedListener
- }
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE_AREA,
- busTimetableCoursesSpinner.selectedItem.toString()
- )
- }
-
- busTimetableRoutesSpinner.setOnItemSelectedListener { _, _, position, _ ->
- shuttleBusTimetableViewModel.setRoutePosition(position)
- if (isRouteInitialization) {
- isRouteInitialization = false
- return@setOnItemSelectedListener
- }
- EventLogger.logClickEvent(
- EventAction.CAMPUS,
- AnalyticsConstant.Label.BUS_TIMETABLE_TIME,
- busTimetableRoutesSpinner.selectedItem.toString()
- )
- }
- }
-
- private fun initViewModel() = with(shuttleBusTimetableViewModel) {
- (requireActivity() as? IProgressDialog)?.withLoading(viewLifecycleOwner, this)
-
- withToastError(this@ShuttleBusTimetableFragment, binding.root)
-
- observeLiveData(busCoursesString) { courses ->
- if (courses.isNullOrEmpty()) {
- binding.busTimetableCoursesSpinner.isVisible = false
- } else {
- binding.busTimetableCoursesSpinner.isVisible = true
- binding.busTimetableCoursesSpinner.adapter =
- ArrayAdapter(
- requireContext(),
- android.R.layout.simple_spinner_dropdown_item,
- courses
- )
- }
- }
-
- observeLiveData(selectedCoursesPosition) {
- binding.busTimetableCoursesSpinner.setSelection(it, true)
- }
-
- observeLiveData(selectedRoutesPosition) {
- binding.busTimetableRoutesSpinner.setSelection(it, true)
- }
-
- observeLiveData(updatedAt) {
- shuttleBusTimetableAdapter.setUpdatedAt(it)
- }
-
- observeLiveData(busRoutes) {
- if (it.isNullOrEmpty()) {
- binding.busTimetableRoutesSpinner.isVisible = false
- } else {
- binding.busTimetableRoutesSpinner.isVisible = true
- binding.busTimetableRoutesSpinner.adapter =
- ArrayAdapter(
- requireContext(),
- android.R.layout.simple_spinner_dropdown_item,
- it
- )
- }
- }
-
- observeLiveData(selectedRoutesPosition) {
- shuttleBusTimetableAdapter.submitList(
- busTimetables.value?.get(
- selectedRoutesPosition.value ?: 0
- )?.map { it.toShuttleBusTimetableUiItem() }
- )
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusRemainTimeUiState.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusRemainTimeUiState.kt
deleted file mode 100644
index 484eff1f9..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusRemainTimeUiState.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.data.util.busNumberFormatted
-import `in`.koreatech.koin.data.util.localized
-import `in`.koreatech.koin.data.util.toBusArrivalTimeFormatted
-import `in`.koreatech.koin.data.util.toBusRemainTimeFormatted
-import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.BusType
-import android.content.Context
-import java.time.LocalTime
-import java.time.format.DateTimeFormatter
-
-data class BusRemainTimeUiState(
- val type: BusType,
- val departure: String,
- val arrival: String,
- val nowBusRemainTime: String,
- val nowBusDepartureTime: String?,
- val nextBusRemainTime: String,
- val nextBusDepartureTime: String?,
- val busNumber: String? = null
-)
-
-fun BusArrivalInfo.ShuttleBusArrivalInfo.toShuttleBusRemainTimeUiState(
- context: Context,
- departure: BusNode,
- arrival: BusNode
-) =
- BusRemainTimeUiState(
- type = BusType.Shuttle,
- departure = departure.localized(context),
- arrival = arrival.localized(context),
- nowBusRemainTime = nowBusRemainTime.toBusRemainTimeFormatted(context),
- nowBusDepartureTime = nowBusArrivalTime?.toBusArrivalTimeFormatted(context),
- nextBusRemainTime = nextBusRemainTime.toBusRemainTimeFormatted(context),
- nextBusDepartureTime = nextBusArrivalTime?.toBusArrivalTimeFormatted(context),
- )
-
-fun BusArrivalInfo.ExpressBusArrivalInfo.toExpressBusRemainTimeUiState(
- context: Context,
- departure: BusNode,
- arrival: BusNode
-) =
- BusRemainTimeUiState(
- type = BusType.Express,
- departure = departure.localized(context),
- arrival = arrival.localized(context),
- nowBusRemainTime = nowBusRemainTime.toBusRemainTimeFormatted(context),
- nowBusDepartureTime = nowBusArrivalTime?.toBusArrivalTimeFormatted(context),
- nextBusRemainTime = nextBusRemainTime.toBusRemainTimeFormatted(context),
- nextBusDepartureTime = nextBusArrivalTime?.toBusArrivalTimeFormatted(context)
- )
-
-fun BusArrivalInfo.CityBusArrivalInfo.toCityBusRemainTimeUiState(
- context: Context,
- departure: BusNode,
- arrival: BusNode
-) =
- BusRemainTimeUiState(
- type = BusType.City,
- departure = departure.localized(context),
- arrival = arrival.localized(context),
- nowBusRemainTime = nowBusRemainTime.toBusRemainTimeFormatted(context),
- nowBusDepartureTime = nowBusArrivalTime?.toBusArrivalTimeFormatted(context),
- nextBusRemainTime = nextBusRemainTime.toBusRemainTimeFormatted(context),
- nextBusDepartureTime = nextBusArrivalTime?.toBusArrivalTimeFormatted(context),
- busNumber = busNumber?.busNumberFormatted(context)
- )
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusSearchResultItem.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusSearchResultItem.kt
deleted file mode 100644
index 4207634c2..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusSearchResultItem.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-import `in`.koreatech.koin.domain.model.bus.BusType
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import java.time.format.DateTimeFormatter
-
-data class BusSearchResultItem(
- val busType: BusType,
- val time: String
-)
-
-fun BusSearchResult.toBusSearchResultItem() = BusSearchResultItem(
- busType = busType,
- time = busTimeString
-)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusTimetableUiItem.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusTimetableUiItem.kt
deleted file mode 100644
index 2ffa760b1..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/BusTimetableUiItem.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-sealed class BusTimetableUiItem {
-
- data class Express(
- val departureTime: String,
- val arrivalTime: String,
- val charge: String
- ) : BusTimetableUiItem()
-
- data class Shuttle(
- val node: String,
- val arrivalTime: String
- ): BusTimetableUiItem()
-
- data class City(
- val startLocation: String,
- val timeInfo: String
- ): BusTimetableUiItem()
-}
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/CityBusTimetableUiItem.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/CityBusTimetableUiItem.kt
deleted file mode 100644
index f7d0c5f95..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/CityBusTimetableUiItem.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-import `in`.koreatech.koin.domain.util.ext.isAm
-import `in`.koreatech.koin.domain.util.ext.isPm
-import java.time.LocalTime
-
-data class CityBusTimetableUiItem(
- val am: String,
- val pm: String
-)
-
-fun List.toCityBusTimetableUiItemList(): List {
- val amList = this.filter { LocalTime.parse(it).isAm() }
- val pmList = this.filter { LocalTime.parse(it).isPm() }
-
- return amList.zip(pmList) { am, pm -> CityBusTimetableUiItem(am, pm) } +
- amList.drop(pmList.size).map { CityBusTimetableUiItem(it, "") } +
- pmList.drop(amList.size).map { CityBusTimetableUiItem("", it) }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/ExpressBusTimetableUiItem.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/ExpressBusTimetableUiItem.kt
deleted file mode 100644
index eb3802b64..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/ExpressBusTimetableUiItem.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import android.content.Context
-
-data class ExpressBusTimetableUiItem(
- val departureTime: String,
- val arrivalTime: String,
- val charge: String
-)
-
-fun BusNodeInfo.ExpressNodeInfo.toExpressBusTimetableUiItem(context: Context) = ExpressBusTimetableUiItem(
- departureTime = departureTime,
- arrivalTime = arrivalTime,
- charge = "$charge${context.getString(R.string.dining_money_unit)}"
-)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/state/ShuttleBusTimetableUiItem.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/state/ShuttleBusTimetableUiItem.kt
deleted file mode 100644
index f07396a59..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/state/ShuttleBusTimetableUiItem.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package `in`.koreatech.koin.ui.bus.state
-
-import `in`.koreatech.koin.R
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import android.content.Context
-import java.time.format.DateTimeFormatter
-
-data class ShuttleBusTimetableUiItem(
- val node: String,
- val arrivalTime: String
-)
-
-fun BusNodeInfo.ShuttleNodeInfo.toShuttleBusTimetableUiItem() = ShuttleBusTimetableUiItem(
- node = node,
- arrivalTime = arrivalTime
-)
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusMainFragmentViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusMainFragmentViewModel.kt
deleted file mode 100644
index fac8aacdf..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusMainFragmentViewModel.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.core.viewmodel.SingleLiveEvent
-import `in`.koreatech.koin.domain.error.bus.BusErrorHandler
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.usecase.bus.timer.GetBusTimerUseCase
-import `in`.koreatech.koin.domain.util.onFailure
-import `in`.koreatech.koin.domain.util.onSuccess
-import android.util.Log
-import androidx.lifecycle.*
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineExceptionHandler
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.plus
-import javax.inject.Inject
-
-@HiltViewModel
-class BusMainFragmentViewModel @Inject constructor(
- private val getBusTimerUseCase: GetBusTimerUseCase,
- private val busErrorHandler: BusErrorHandler
-) : BaseViewModel() {
-
- private val _departure = MutableLiveData(BusNode.Koreatech)
- private val _arrival = MutableLiveData(BusNode.Terminal)
- var isUserSelection = false
-
- val departure: LiveData get() = _departure
- val arrival: LiveData get() = _arrival
-
- val busTimer = liveData {
- try {
- departure.asFlow().combine(arrival.asFlow()) { departure: BusNode, arrival: BusNode ->
- departure to arrival
- }
- .flatMapLatest { (departure, arrival) ->
- getBusTimerUseCase(departure, arrival)
- }
- .collect { result ->
- emit(result)
- }
- } catch (_: CancellationException) {
- } catch (e: Exception) {
- _errorToast.value = busErrorHandler.handleGetBusRemainTimeError(e).message
- }
- }
-
- fun setDeparture(departure: BusNode) {
- if (departure == _arrival.value) {
- _arrival.value = _departure.value
- }
- _departure.value = departure
- }
-
- fun setArrival(arrival: BusNode) {
- if (arrival == _departure.value) {
- _departure.value = _arrival.value
- }
- _arrival.value = arrival
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusSearchViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusSearchViewModel.kt
deleted file mode 100644
index ef8967a0b..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusSearchViewModel.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.core.viewmodel.SingleLiveEvent
-import `in`.koreatech.koin.domain.model.bus.BusNode
-import `in`.koreatech.koin.domain.model.bus.search.BusSearchResult
-import `in`.koreatech.koin.domain.usecase.bus.search.SearchBusUseCase
-import `in`.koreatech.koin.domain.util.onFailure
-import `in`.koreatech.koin.domain.util.onSuccess
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.LocalTime
-import javax.inject.Inject
-
-@HiltViewModel
-class BusSearchViewModel @Inject constructor(
- private val searchBusUseCase: SearchBusUseCase
-) : BaseViewModel() {
- private val _selectedDate = MutableLiveData(LocalDate.now())
- private val _selectedTime = MutableLiveData(LocalTime.now())
- private val _departure = MutableLiveData(BusNode.Koreatech)
- private val _arrival = MutableLiveData(BusNode.Terminal)
-
- private val _busSearchResult = SingleLiveEvent>()
-
- val selectedDate: LiveData get() = _selectedDate
- val selectedTime: LiveData get() = _selectedTime
- val departure: LiveData get() = _departure
- val arrival: LiveData get() = _arrival
-
- val busSearchResult: LiveData> get() = _busSearchResult
-
- fun setSelectedDate(localDate: LocalDate) {
- _selectedDate.value = localDate
- }
-
- fun setSelectedTime(localTime: LocalTime) {
- _selectedTime.value = localTime
- }
-
- fun setDeparture(busNode: BusNode) {
- if (busNode == _arrival.value) {
- _arrival.value = _departure.value
- }
- _departure.value = busNode
- }
-
- fun setArrival(busNode: BusNode) {
- if (busNode == _departure.value) {
- _departure.value = _arrival.value
- }
- _arrival.value = busNode
- }
-
- fun search() = viewModelScope.launchWithLoading {
- searchBusUseCase.invoke(
- dateTime = LocalDateTime.of(
- selectedDate.value, selectedTime.value
- ),
- departure = departure.value ?: BusNode.Koreatech,
- arrival = arrival.value ?: BusNode.Terminal
- ).onSuccess {
- _busSearchResult.value = it
- }.onFailure {
- _errorToast.value = it.message
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusTimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusTimetableViewModel.kt
deleted file mode 100644
index 26bb13d53..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/BusTimetableViewModel.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import dagger.hilt.android.lifecycle.HiltViewModel
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.domain.model.bus.BusType
-import javax.inject.Inject
-
-@HiltViewModel
-class BusTimetableViewModel @Inject constructor() : BaseViewModel() {
- private val _showingBusType = MutableLiveData()
- val showingBusType: LiveData get() = _showingBusType
-
- fun setBusType(busType: BusType) {
- _showingBusType.value = busType
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/CityBusTimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/CityBusTimetableViewModel.kt
deleted file mode 100644
index b6fc7c272..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/CityBusTimetableViewModel.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.domain.model.bus.city.CityBusGeneralDestination
-import `in`.koreatech.koin.domain.model.bus.city.CityBusNumber
-import `in`.koreatech.koin.domain.model.bus.city.posToCityBusNumber
-import `in`.koreatech.koin.domain.model.bus.city.toCityBusGeneralDestination
-import `in`.koreatech.koin.domain.model.bus.city.toCityBusInfo
-import `in`.koreatech.koin.domain.usecase.bus.timetable.city.GetCityBusTimetableUseCase
-import `in`.koreatech.koin.domain.util.onFailure
-import `in`.koreatech.koin.domain.util.onSuccess
-import javax.inject.Inject
-
-@HiltViewModel
-class CityBusTimetableViewModel @Inject constructor(
- private val getCityBusTimetableUseCase: GetCityBusTimetableUseCase,
-) : BaseViewModel() {
- private val _busNumber: MutableLiveData = MutableLiveData(CityBusNumber.Bus400)
- val busNumber: LiveData get() = _busNumber
- private val _destination: MutableLiveData =
- MutableLiveData(CityBusGeneralDestination.Beongchon)
- val destination: LiveData get() = _destination
- private val _busDepartTimes = MutableLiveData>()
- val busDepartTimes: LiveData> get() = _busDepartTimes
- private val _updatedAt = MutableLiveData()
- val updatedAt: LiveData get() = _updatedAt
-
- init {
- viewModelScope.launchWithLoading {
- updateCityBusTimetable()
- }
- }
-
- fun setBusNumber(position: Int) = viewModelScope.launchWithLoading {
- _busNumber.value = position.posToCityBusNumber
- updateCityBusTimetable()
- }
-
- fun setDestination(position: Int) = viewModelScope.launchWithLoading {
- _destination.value = position.toCityBusGeneralDestination
- updateCityBusTimetable()
- }
-
- private suspend fun updateCityBusTimetable() {
- val infoPair = Pair(busNumber.value!!, destination.value!!)
- getCityBusTimetableUseCase(infoPair.toCityBusInfo())
- .onSuccess {
- _busNumber.value = it.busInfos.busNumber
- _destination.value = it.busInfos.arrivalNode
- _busDepartTimes.value = it.departTimes
- _updatedAt.value = it.updatedAt
- }
- .onFailure {
- _errorToast.value = it.message
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ExpressBusTimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ExpressBusTimetableViewModel.kt
deleted file mode 100644
index 1b93e125f..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ExpressBusTimetableViewModel.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import `in`.koreatech.koin.domain.usecase.bus.timetable.express.GetExpressBusCoursesUseCase
-import `in`.koreatech.koin.domain.usecase.bus.timetable.express.GetExpressBusTimetableUseCase
-import `in`.koreatech.koin.domain.util.onSuccess
-import `in`.koreatech.koin.util.ext.onFailureToast
-import javax.inject.Inject
-
-@HiltViewModel
-class ExpressBusTimetableViewModel @Inject constructor(
- private val getExpressBusTimetableUseCase: GetExpressBusTimetableUseCase,
- private val getExpressBusCoursesUseCase: GetExpressBusCoursesUseCase
-) : BaseViewModel() {
- private val busCourses = mutableListOf>()
-
- private val _busCoursesString = MutableLiveData>()
- private val _busTimetables = MutableLiveData>()
- private val _selectedCoursesPosition = MutableLiveData(0)
- private val _updatedAt = MutableLiveData()
-
- val busCoursesString: LiveData> get() = _busCoursesString
- val busTimetables: LiveData> get() = _busTimetables
-
- val selectedCoursesPosition: LiveData get() = _selectedCoursesPosition
- val updatedAt: LiveData get() = _updatedAt
-
- init {
- viewModelScope.launchWithLoading {
- updateExpressBusCourse()
- updateExpressBusTimetable()
- }
- }
-
- fun setCoursePosition(position: Int) = viewModelScope.launchWithLoading {
- _selectedCoursesPosition.value = position
- updateExpressBusTimetable()
- }
-
- private suspend fun updateExpressBusCourse() {
- getExpressBusCoursesUseCase()
- .onSuccess { courses ->
- busCourses.clear()
- busCourses.addAll(courses)
- _busCoursesString.value = courses.map { (_, name) -> name }
- }
- .onFailureToast(this)
- }
-
- private suspend fun updateExpressBusTimetable() {
- if (busCourses.isNotEmpty()) {
- getExpressBusTimetableUseCase(
- busCourses[selectedCoursesPosition.value ?: 0].first
- )
- .onSuccess {
- _updatedAt.value = it.updatedAt
- _busTimetables.value = it.routes.arrivalInfo
- }
- .onFailureToast(this)
- }
- }
-}
\ No newline at end of file
diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ShuttleBusTimetableViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ShuttleBusTimetableViewModel.kt
deleted file mode 100644
index a318f8ba7..000000000
--- a/koin/src/main/java/in/koreatech/koin/ui/bus/viewmodel/ShuttleBusTimetableViewModel.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package `in`.koreatech.koin.ui.bus.viewmodel
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
-import `in`.koreatech.koin.core.viewmodel.BaseViewModel
-import `in`.koreatech.koin.domain.model.bus.course.BusCourse
-import `in`.koreatech.koin.domain.model.bus.timetable.BusNodeInfo
-import `in`.koreatech.koin.domain.model.bus.timetable.BusTimetable
-import `in`.koreatech.koin.domain.usecase.bus.timetable.shuttle.GetShuttleBusCoursesUseCase
-import `in`.koreatech.koin.domain.usecase.bus.timetable.shuttle.GetShuttleBusTimetableUseCase
-import `in`.koreatech.koin.domain.util.onSuccess
-import `in`.koreatech.koin.util.ext.onFailureToast
-import javax.inject.Inject
-
-@HiltViewModel
-class ShuttleBusTimetableViewModel @Inject constructor(
- private val getShuttleBusTimetableUseCase: GetShuttleBusTimetableUseCase,
- private val getShuttleBusCoursesUseCase: GetShuttleBusCoursesUseCase,
-) : BaseViewModel() {
- private val busCourses = mutableListOf>()
-
- private val _busCoursesString = MutableLiveData>()
- private val _selectedCoursesPosition = MutableLiveData(0)
- private val _selectedRoutesPosition = MutableLiveData(0)
- private val _updatedAt = MutableLiveData()
- private val _busRoutes = MutableLiveData>()
- private val _busTimetables = MutableLiveData
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/koin/src/main/res/layout/bus_main_fragment.xml b/koin/src/main/res/layout/bus_main_fragment.xml
deleted file mode 100644
index 8e156e47f..000000000
--- a/koin/src/main/res/layout/bus_main_fragment.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/koin/src/main/res/layout/bus_timetable_fragment.xml b/koin/src/main/res/layout/bus_timetable_fragment.xml
deleted file mode 100644
index a8f6d9c72..000000000
--- a/koin/src/main/res/layout/bus_timetable_fragment.xml
+++ /dev/null
@@ -1,175 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/koin/src/main/res/layout/bus_timetable_search_fragment.xml b/koin/src/main/res/layout/bus_timetable_search_fragment.xml
deleted file mode 100644
index 0309ebd77..000000000
--- a/koin/src/main/res/layout/bus_timetable_search_fragment.xml
+++ /dev/null
@@ -1,264 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/koin/src/main/res/layout/bus_timetable_search_result_dialog.xml b/koin/src/main/res/layout/bus_timetable_search_result_dialog.xml
deleted file mode 100644
index e771f25a0..000000000
--- a/koin/src/main/res/layout/bus_timetable_search_result_dialog.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/koin/src/main/res/layout/bus_timetable_search_result_item.xml b/koin/src/main/res/layout/bus_timetable_search_result_item.xml
deleted file mode 100644
index dca008ba7..000000000
--- a/koin/src/main/res/layout/bus_timetable_search_result_item.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/koin/src/main/res/menu/menu_drawer_left.xml b/koin/src/main/res/menu/menu_drawer_left.xml
index b2eb943f8..98bc88945 100644
--- a/koin/src/main/res/menu/menu_drawer_left.xml
+++ b/koin/src/main/res/menu/menu_drawer_left.xml
@@ -13,9 +13,6 @@
-
diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml
index 9505d209b..3fc147854 100644
--- a/koin/src/main/res/values/strings.xml
+++ b/koin/src/main/res/values/strings.xml
@@ -15,7 +15,6 @@
상점
- 버스/교통
식단
동아리
복덕방
diff --git a/settings.gradle b/settings.gradle
index 78a3a120c..39b0a10fe 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -28,3 +28,4 @@ include ':core:onboarding'
include ':feature:timetable'
include ':core:designsystem'
include ':feature:bus'
+include ':core:analytics'