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>>() - - val busCoursesString: LiveData> get() = _busCoursesString - - val selectedCoursesPosition: LiveData get() = _selectedCoursesPosition - val selectedRoutesPosition: LiveData get() = _selectedRoutesPosition - val updatedAt: LiveData get() = _updatedAt - val busRoutes: LiveData> get() = _busRoutes - val busTimetables: LiveData>> get() = _busTimetables - - init { - viewModelScope.launchWithLoading { - getShuttleBusCourse() - updateShuttleBusRoutes() - } - } - - fun setCoursePosition(position: Int) = viewModelScope.launchWithLoading { - _selectedCoursesPosition.value = position - updateShuttleBusRoutes() - if(!busRoutes.value.isNullOrEmpty()) setRoutePosition(0) - } - - fun setRoutePosition(position: Int) = viewModelScope.launchWithLoading { - _selectedRoutesPosition.value = position - } - - private suspend fun getShuttleBusCourse() { - getShuttleBusCoursesUseCase() - .onSuccess { courses -> - busCourses.clear() - busCourses.addAll(courses.filter { !it.second.contains("te") }) - _busCoursesString.value = courses.filter { !it.second.contains("te") }.map { (_, name) -> name } - } - .onFailureToast(this) - } - - private suspend fun updateShuttleBusRoutes() { - if (busCourses.isNotEmpty()) { - getShuttleBusTimetableUseCase( - busCourses[selectedCoursesPosition.value ?: 0].first, - ) - .onSuccess { - _updatedAt.value = it.updatedAt - _busRoutes.value = it.routes.map { route -> route.routeName } - updateShuttleBusTimetable(it) - } - .onFailureToast(this) - } - } - - private fun updateShuttleBusTimetable(timetable: BusTimetable.ShuttleBusTimetable) { - _busTimetables.value = - mutableMapOf>() - .apply { - timetable.routes.forEachIndexed { idx, route -> - set(idx, route.arrivalInfo) - } - } - } -} \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt index ce0d7fa8f..adb9b28de 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt @@ -4,9 +4,7 @@ import android.content.Intent import android.os.Bundle import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager.widget.ViewPager import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback @@ -16,7 +14,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.appbar.AppBarBase -import `in`.koreatech.koin.core.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.onboarding.OnboardingManager import `in`.koreatech.koin.core.onboarding.OnboardingType import `in`.koreatech.koin.core.util.dataBinding diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningItemsFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningItemsFragment.kt index e3b7b4eff..739334cea 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningItemsFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningItemsFragment.kt @@ -26,7 +26,7 @@ import `in`.koreatech.koin.R import `in`.koreatech.koin.core.abtest.ExperimentGroup 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.onboarding.OnboardingManager import `in`.koreatech.koin.core.onboarding.OnboardingType import `in`.koreatech.koin.core.util.dataBinding diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt index 418261826..945248de0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt @@ -3,12 +3,9 @@ package `in`.koreatech.koin.ui.dining.adapter import android.content.Context import android.graphics.drawable.Drawable import android.util.TypedValue -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -20,7 +17,7 @@ import com.bumptech.glide.request.target.Target 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.ImageZoomableDialog import `in`.koreatech.koin.databinding.ItemDiningBinding import `in`.koreatech.koin.domain.constant.BREAKFAST diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningOriginalAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningOriginalAdapter.kt index 2a24a8e2b..89092c154 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningOriginalAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningOriginalAdapter.kt @@ -3,12 +3,9 @@ package `in`.koreatech.koin.ui.dining.adapter import android.content.Context import android.graphics.drawable.Drawable import android.util.TypedValue -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -20,9 +17,8 @@ import com.bumptech.glide.request.target.Target 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.ImageZoomableDialog -import `in`.koreatech.koin.databinding.ItemDiningBinding import `in`.koreatech.koin.databinding.ItemDiningOriginalBinding import `in`.koreatech.koin.domain.constant.BREAKFAST import `in`.koreatech.koin.domain.model.dining.Dining diff --git a/koin/src/main/java/in/koreatech/koin/ui/land/LandActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/land/LandActivity.kt index 512e7b831..fbf0068c7 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/land/LandActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/land/LandActivity.kt @@ -104,8 +104,12 @@ class LandActivity : KoinNavigationDrawerActivity(), OnMapReadyCallback { if (mapFragment == null) { mapFragment = NaverMapFragment().newInstance(options) } - supportFragmentManager.beginTransaction().add(R.id.activity_land_navermap, mapFragment!!) - .commit() + + if (!mapFragment!!.isAdded) { + supportFragmentManager.beginTransaction().add(R.id.activity_land_navermap, mapFragment!!) + .commit() + } + mapFragment.getMapAsync(this) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/land/LandDetailActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/land/LandDetailActivity.kt index ea8b2d729..f8d9de5e3 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/land/LandDetailActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/land/LandDetailActivity.kt @@ -1,15 +1,5 @@ package `in`.koreatech.koin.ui.land -import `in`.koreatech.koin.R -import `in`.koreatech.koin.constant.LAND -import `in`.koreatech.koin.core.appbar.AppBarBase -import `in`.koreatech.koin.databinding.LandActivityDetailBinding -import `in`.koreatech.koin.domain.model.land.LandDetail -import `in`.koreatech.koin.ui.land.adapter.LandDetailViewPagerAdapter -import `in`.koreatech.koin.ui.land.viewmodel.LandDetailViewModel -import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity -import `in`.koreatech.koin.ui.navigation.state.MenuState -import `in`.koreatech.koin.util.ext.dpToIntPx import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.os.Bundle @@ -29,6 +19,16 @@ import com.naver.maps.map.NaverMapOptions import com.naver.maps.map.OnMapReadyCallback import com.naver.maps.map.overlay.Marker import com.naver.maps.map.overlay.OverlayImage +import `in`.koreatech.koin.R +import `in`.koreatech.koin.constant.LAND +import `in`.koreatech.koin.core.appbar.AppBarBase +import `in`.koreatech.koin.databinding.LandActivityDetailBinding +import `in`.koreatech.koin.domain.model.land.LandDetail +import `in`.koreatech.koin.ui.land.adapter.LandDetailViewPagerAdapter +import `in`.koreatech.koin.ui.land.viewmodel.LandDetailViewModel +import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity +import `in`.koreatech.koin.ui.navigation.state.MenuState +import `in`.koreatech.koin.util.ext.dpToIntPx class LandDetailActivity : KoinNavigationDrawerActivity(), OnMapReadyCallback { override val menuState = MenuState.Land @@ -204,8 +204,10 @@ class LandDetailActivity : KoinNavigationDrawerActivity(), OnMapReadyCallback { if (mapFragment == null) { mapFragment = NaverMapFragment().newInstance(options) } - supportFragmentManager.beginTransaction() - .add(R.id.activity_land_detail_navermap, mapFragment!!).commit() + if (!mapFragment!!.isAdded) { + supportFragmentManager.beginTransaction() + .add(R.id.activity_land_detail_navermap, mapFragment!!).commit() + } mapFragment.getMapAsync(this) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt index 300e042b0..19b5c2edb 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt @@ -15,7 +15,7 @@ import `in`.koreatech.koin.common.UiStatus 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.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityLoginBinding import `in`.koreatech.koin.ui.businesslogin.BusinessLoginActivity diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 5c4e36787..35cb4dcbb 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -13,15 +13,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -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.bus.BusSearchActivity import `in`.koreatech.bus.BusTimetableActivity import `in`.koreatech.bus.screen.MainEntryView +import `in`.koreatech.koin.BuildConfig import `in`.koreatech.koin.R import `in`.koreatech.koin.core.abtest.Experiment import `in`.koreatech.koin.core.abtest.ExperimentGroup @@ -30,7 +28,7 @@ import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra 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.navigation.Navigator import `in`.koreatech.koin.core.navigation.SchemeType import `in`.koreatech.koin.core.navigation.utils.EXTRA_ID @@ -39,22 +37,15 @@ import `in`.koreatech.koin.core.onboarding.ArrowDirection import `in`.koreatech.koin.core.onboarding.OnboardingManager import `in`.koreatech.koin.core.onboarding.OnboardingType import `in`.koreatech.koin.core.util.dataBinding -import `in`.koreatech.koin.core.viewpager.HorizontalMarginItemDecoration import `in`.koreatech.koin.core.viewpager.enableAutoScroll -import `in`.koreatech.koin.data.constant.URLConstant -import `in`.koreatech.koin.data.util.localized import `in`.koreatech.koin.data.util.todayOrTomorrow import `in`.koreatech.koin.databinding.ActivityMainBinding -import `in`.koreatech.koin.domain.model.bus.BusType -import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo import `in`.koreatech.koin.domain.model.dining.DiningPlace import `in`.koreatech.koin.domain.model.store.StoreCategories import `in`.koreatech.koin.ui.article.ArticleActivity -import `in`.koreatech.koin.ui.bus.BusActivity import `in`.koreatech.koin.ui.dining.DiningActivity -import `in`.koreatech.koin.ui.main.adapter.BusPagerAdapter -import `in`.koreatech.koin.ui.main.adapter.DiningContainerViewPager2Adapter import `in`.koreatech.koin.ui.main.adapter.ArticleMainAdapter +import `in`.koreatech.koin.ui.main.adapter.DiningContainerViewPager2Adapter import `in`.koreatech.koin.ui.main.adapter.StoreCategoriesRecyclerAdapter import `in`.koreatech.koin.ui.main.viewmodel.MainActivityViewModel import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerTimeActivity @@ -99,51 +90,6 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } ) - private val busPagerAdapter = BusPagerAdapter().apply { - setOnCardClickListener { - callDrawerItem(R.id.navi_item_bus, Bundle()) - EventLogger.logClickEvent( - EventAction.CAMPUS, - AnalyticsConstant.Label.MAIN_BUS, - getString(R.string.bus) - ) - } - setOnSwitchClickListener { - viewModel.switchBusNode() - EventLogger.logClickEvent( - EventAction.CAMPUS, - AnalyticsConstant.Label.MAIN_BUS_CHANGETOFROM, - it.localized(this@MainActivity) - ) - } - setOnGotoClickListener { type -> - when (type) { - BusType.Shuttle -> { - Intent(this@MainActivity, WebViewActivity::class.java).apply { - putExtra("url", URLConstant.UNIBUS) - }.run(::startActivity) - } - - BusType.Express -> { - Intent(this@MainActivity, BusActivity::class.java).apply { - putExtra("tab", 2) - putExtra("timetableMenu", type.busTypeString) - }.run(::startActivity) - } - - BusType.City -> { - Intent(this@MainActivity, BusActivity::class.java).apply { - putExtra("tab", 2) - putExtra("timetableMenu", type.busTypeString) - }.run(::startActivity) - } - - else -> {} - } - } - } - private lateinit var busViewPagerScrollCallback: ViewPager2.OnPageChangeCallback - private val diningContainerAdapter by lazy { DiningContainerViewPager2Adapter(this) } private val storeCategoriesRecyclerAdapter = StoreCategoriesRecyclerAdapter().apply { @@ -249,31 +195,29 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { startActivity(Intent(this@MainActivity, ArticleActivity::class.java)) } - busViewPager.apply { - adapter = busPagerAdapter - offscreenPageLimit = 3 - currentItem = Int.MAX_VALUE / 2 - - val nextItemPx = resources.getDimension(R.dimen.view_pager_next_item_visible_dp) - val currentItemMarginPx = resources.getDimension(R.dimen.view_pager_item_margin) - - addItemDecoration( - HorizontalMarginItemDecoration( - this@MainActivity, - R.dimen.view_pager_item_margin - ) - ) - } - busComposeView.apply { setContent { MainEntryView( onShuttleTicketClicked = { - // TODO : 유니버스 바로가기 + EventLogger.logCampusClickEvent( + "shuttle_ticket", + "셔틀 탑승권" + ) + val intent = Intent(this@MainActivity, WebViewActivity::class.java) + intent.putExtra("url", "https://koreatech.unibus.kr/") + startActivity(intent) }, onTimetableCardClicked = { + EventLogger.logCampusClickEvent( + "main_bus_timetable", + "버스 시간표 바로가기" + ) val intent = Intent(this@MainActivity, BusTimetableActivity::class.java) startActivity(intent) }, onSearchCardClicked = { + EventLogger.logCampusClickEvent( + "main_bus_search", + "가장 빠른 버스 조회하기" + ) val intent = Intent(this@MainActivity, BusSearchActivity::class.java) startActivity(intent) }, @@ -365,13 +309,6 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { binding.textViewDiningTodayOrTomorrow.text = it.todayOrTomorrow(this@MainActivity) } - - observeLiveData(busTimer) { - busPagerAdapter.setBusTimerItems(it) - if (this@MainActivity::busViewPagerScrollCallback.isInitialized.not()) { - initBusViewPagerScrollCallback(it) - } - } observeLiveData(storeCategories) { storeCategoriesRecyclerAdapter.submitList(it) } @@ -379,23 +316,6 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { binding.storeButtonLayout.visibility = View.VISIBLE } - private fun initBusViewPagerScrollCallback(busArrivalInfos: List) { - busViewPagerScrollCallback = object : ViewPager2.OnPageChangeCallback() { - var prev = 0 - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - EventLogger.logScrollEvent( - EventAction.CAMPUS, - AnalyticsConstant.Label.MAIN_BUS_SCROLL, - busArrivalInfos[prev % 3].localized(this@MainActivity) + ">" + busArrivalInfos[position % 3].localized( - this@MainActivity - ) - ) - prev = position - } - }.also { binding.busViewPager.registerOnPageChangeCallback(it) } - } - private fun initDiningTooltip() { with(onboardingManager) { showOnboardingTooltipIfNeeded( @@ -478,10 +398,4 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } } } - - override fun onDestroy() { - super.onDestroy() - if (this@MainActivity::busViewPagerScrollCallback.isInitialized) - binding.busViewPager.unregisterOnPageChangeCallback(busViewPagerScrollCallback) - } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt deleted file mode 100644 index 0f2412c04..000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt +++ /dev/null @@ -1,142 +0,0 @@ -package `in`.koreatech.koin.ui.main.adapter - -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.databinding.MainCardBusBinding -import `in`.koreatech.koin.domain.model.bus.BusType -import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo -import `in`.koreatech.koin.domain.model.bus.timer.busType -import android.content.Context -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView - -class BusPagerAdapter : RecyclerView.Adapter() { - override fun getItemCount(): Int = Int.MAX_VALUE - - private val busItems = mutableListOf() - - var onSwitchClickListener: OnSwitchClickListener? = null - var onCardClickListener: OnCardClickListener? = null - var onGotoClickListener: OnGotoClickListener? = null - - inner class MainCardBusViewHolder( - val binding: MainCardBusBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(busArrivalInfo: BusArrivalInfo) { - with(binding) { - root.setOnClickListener { onCardClickListener?.onCardClick(busArrivalInfo.busType) } - imageButtonSwitch.setOnClickListener { onSwitchClickListener?.onSwitchClick(busArrivalInfo) } - busGotoLayout.setOnClickListener { onGotoClickListener?.onGotoClick(busArrivalInfo.busType) } - - textViewDepartures.text = busArrivalInfo.departure.localized(root.context) - textViewArrival.text = busArrivalInfo.arrival.localized(root.context) - textViewBusType.text = busArrivalInfo.localized(root.context) - textViewRemainingTime.text = - busArrivalInfo.nowBusRemainTime.toBusRemainTimeFormatted(root.context) - - when (busArrivalInfo) { - is BusArrivalInfo.ShuttleBusArrivalInfo -> { - textViewBusGoto.setText(R.string.bus_goto_unibus) - } - is BusArrivalInfo.ExpressBusArrivalInfo -> { - textViewBusGoto.setText(R.string.bus_goto_timetable) - } - is BusArrivalInfo.CityBusArrivalInfo -> { - textViewBusGoto.setText(R.string.bus_goto_timetable) -// val info = busArrivalInfo.busNumber?.busNumberFormatted( -// root.context -// ) -// if(info == null) { -// textViewBusInfo.isVisible = false -// } else { -// textViewBusInfo.isVisible = true -// textViewBusInfo.text = info -// } - } - else -> { - textViewBusGoto.setText(R.string.bus_goto_timetable) -// val info = busArrivalInfo.nowBusArrivalTime?.toBusArrivalTimeFormatted(root.context) -// if(info == null) { -// textViewBusInfo.isVisible = false -// } else { -// textViewBusInfo.isVisible = true -// textViewBusInfo.text = info -// } - } - } - - busTypeLayout.setBackgroundColor( - when (busArrivalInfo) { - is BusArrivalInfo.CityBusArrivalInfo -> - ContextCompat.getColor(root.context, R.color.green3) - is BusArrivalInfo.CommutingBusArrivalInfo, is BusArrivalInfo.ShuttleBusArrivalInfo -> - ContextCompat.getColor(root.context, R.color.colorAccent) - is BusArrivalInfo.ExpressBusArrivalInfo -> - ContextCompat.getColor(root.context, R.color.blue5) - } - ) - } - } - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): MainCardBusViewHolder { - return MainCardBusViewHolder(MainCardBusBinding.inflate(LayoutInflater.from(parent.context), parent, false)) - } - - override fun onBindViewHolder(holder: MainCardBusViewHolder, position: Int) { - if(busItems.size >= 3) { - holder.bind(busItems[position % 3]) - } - } - - fun setBusTimerItems(items: List) { - busItems.clear() - busItems.addAll(items) - notifyDataSetChanged() - } - - inline fun setOnSwitchClickListener(crossinline onSwitchClick: (BusArrivalInfo) -> Unit) { - onSwitchClickListener = object : OnSwitchClickListener { - override fun onSwitchClick(busArrivalInfo: BusArrivalInfo) { - onSwitchClick(busArrivalInfo) - } - } - } - - inline fun setOnCardClickListener(crossinline onCardClick: (BusType) -> Unit) { - onCardClickListener = object : OnCardClickListener { - override fun onCardClick(busType: BusType) { - onCardClick(busType) - } - } - } - - inline fun setOnGotoClickListener(crossinline onGotoClick: (BusType) -> Unit) { - onGotoClickListener = object : OnGotoClickListener { - override fun onGotoClick(busType: BusType) { - onGotoClick(busType) - } - } - } - - interface OnSwitchClickListener { - fun onSwitchClick(busArrivalInfo: BusArrivalInfo) - } - - interface OnCardClickListener { - fun onCardClick(busType: BusType) - } - - interface OnGotoClickListener { - fun onGotoClick(busType: BusType) - } -} diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/fragment/DiningContainerFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/main/fragment/DiningContainerFragment.kt index c1cdea4e7..823066230 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/fragment/DiningContainerFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/fragment/DiningContainerFragment.kt @@ -10,7 +10,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.util.dataBinding import `in`.koreatech.koin.data.util.localized import `in`.koreatech.koin.databinding.FragmentDiningContainerBinding diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/presenter/MainActivityPresenter.java b/koin/src/main/java/in/koreatech/koin/ui/main/presenter/MainActivityPresenter.java deleted file mode 100644 index 5ac136173..000000000 --- a/koin/src/main/java/in/koreatech/koin/ui/main/presenter/MainActivityPresenter.java +++ /dev/null @@ -1,199 +0,0 @@ -package in.koreatech.koin.ui.main.presenter; - -import android.util.Log; - -import java.text.ParseException; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -import in.koreatech.koin.constant.BusType; -import in.koreatech.koin.core.network.ApiCallback; -import in.koreatech.koin.data.network.entity.Bus; -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.BusInteractor; -import in.koreatech.koin.data.network.interactor.DiningInteractor; -import in.koreatech.koin.data.network.interactor.TermInteractor; -import in.koreatech.koin.data.response.bus.BusResponse; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; - -public class MainActivityPresenter implements MainActivityContact.Presenter { - private CompositeDisposable compositeDisposable = new CompositeDisposable(); - private static final String TAG = MainActivityPresenter.class.getSimpleName(); - - private final MainActivityContact.View view; - private final BusInteractor busInteractor; - private final TermInteractor termInteractor; - private final DiningInteractor diningInteractor; - private final ApiCallback apiCallbackCitybus = new ApiCallback() { //시내버스의 시간을 받아오는 api callback - @Override - public void onSuccess(Object object) { - try { - BusResponse busResponse = (BusResponse) object; - view.updateCityBusDepartInfo(busResponse.getNowBus().getBusNumber()); - view.updateCityBusTime((int) busResponse.getNowBus().getRemainTimeSecond()); - - view.hideLoading(); - } catch (Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Citybus Failed", ""); - view.updateFailCityBusDepartInfo(); - view.hideLoading(); - } - } - - @Override - public void onFailure(Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Citybus Failed", ""); - view.updateFailCityBusDepartInfo(); - view.hideLoading(); - } - }; - - private final ApiCallback apiCallbackDaesung = new ApiCallback() { //시내버스의 시간을 받아오는 api callback - @Override - public void onSuccess(Object object) { - try { - BusResponse busResponse = (BusResponse) object; - LocalTime localTime = LocalTime.now().plusSeconds(busResponse.getNowBus().getRemainTimeSecond()).plusMinutes(1);; - view.updateDaesungBusDepartInfo(localTime.format(DateTimeFormatter.ofPattern("HH:mm"))); - view.updateDaesungBusTime((int) busResponse.getNowBus().getRemainTimeSecond()); - - view.hideLoading(); - } catch (Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Daesung Failed", ""); - view.updateFailDaesungBusDepartInfo(); - view.hideLoading(); - } - } - - @Override - public void onFailure(Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Daesung Failed", ""); - view.updateFailDaesungBusDepartInfo(); - view.hideLoading(); - } - }; - - private final ApiCallback apiCallbackShuttle = new ApiCallback() { //시내버스의 시간을 받아오는 api callback - @Override - public void onSuccess(Object object) { - try { - BusResponse busResponse = (BusResponse) object; - LocalTime localTime = LocalTime.now().plusSeconds(busResponse.getNowBus().getRemainTimeSecond()).plusMinutes(1); - view.updateShuttleBusDepartInfo(localTime.format(DateTimeFormatter.ofPattern("HH:mm"))); - view.updateShuttleBusTime((int) busResponse.getNowBus().getRemainTimeSecond()); - - view.hideLoading(); - } catch (Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Shuttle Failed", ""); - view.updateFailShuttleBusDepartInfo(); - view.hideLoading(); - } - } - - @Override - public void onFailure(Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - Log.e("Shuttle Failed", ""); - view.updateFailShuttleBusDepartInfo(); - view.hideLoading(); - } - }; - private final ApiCallback termApiCallback = new ApiCallback() { //방학인지 학기중인지 정보를 받아오는 api callback - @Override - public void onSuccess(Object object) { - Term term = (Term) object; - view.updateShuttleBusInfo(term.getTerm()); //셔틀버스의 남은 시간을 계산하여 업데이트 - view.hideLoading(); - } - - @Override - public void onFailure(Throwable throwable) { - Log.d(TAG, throwable.getMessage()); - view.updateFailCityBusDepartInfo(); - view.hideLoading(); - } - }; - - public MainActivityPresenter(MainActivityContact.View view, BusInteractor busInteractor, TermInteractor termInteractor, DiningInteractor diningInteractor) { - this.view = view; - this.busInteractor = busInteractor; - this.termInteractor = termInteractor; - this.diningInteractor = diningInteractor; - } - - /** - * 학기 정보를 요청하는 함수 - * 학기에 따라 셔틀버스 시간표가 달라져 셔틀버스 정보를 얻어오기전에 먼저 실행되어야 한다. - */ - public void getTermInfo() { - termInteractor.readTerm(termApiCallback); - } - - public void getCityBus(int depart, int arrival) { - view.showLoading(); - String departString = getBusNodeString(depart); - String arrivalString = getBusNodeString(arrival); - - compositeDisposable.add(busInteractor.readCityBusList(apiCallbackCitybus, departString, arrivalString)); - } - - public void getDaesungBus(int depart, int arrival) { - view.showLoading(); - String departString = getBusNodeString(depart); - String arrivalString = getBusNodeString(arrival); - - compositeDisposable.add(busInteractor.readDaesungBusList(apiCallbackDaesung, departString, arrivalString)); - } - - /** - * 셔틀버스의 시간표를 계산해주는 함수 - * 시내버스와 학기정보와의 관계는 없다. 그러나 방학때는 셔틀버스의 시간이 달라짐으로 학기정보와 셔틀버스 시간은 관계가 있다. - * isVacation으로 방학인지 학기중인지 구별 - * - * @param depart 출발지 - * @param arrival 도착지 - * @param term 학기 정보 - */ - public void getShuttleBus(int depart, int arrival, int term) { - view.showLoading(); - String departString = getBusNodeString(depart); - String arrivalString = getBusNodeString(arrival); - - compositeDisposable.add(busInteractor.readShuttleBusList(apiCallbackShuttle, departString, arrivalString)); - } - - @Override - public void getDiningList(String date) { - compositeDisposable.add( - diningInteractor.readDingingList(date) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnSubscribe(disposable -> view.showLoading()) - .doOnComplete(view::hideLoading) - .subscribe(view::onDiningListDataReceived, throwable -> view.showEmptyDining())); - } - - private String getBusNodeString(int node) { - if (node == 0) - return BusType.KOREATECH.getDestination(); - else if (node == 1) - return BusType.TERMINAL.getDestination(); - else - return BusType.STATION.getDestination(); - } - - @Override - public void dispose() { - compositeDisposable.dispose(); - } -} diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt index 5325b8f92..72e9ae31d 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt @@ -2,23 +2,18 @@ package `in`.koreatech.koin.ui.main.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asFlow -import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.abtest.Experiment import `in`.koreatech.koin.core.abtest.ExperimentGroup 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.viewmodel.BaseViewModel -import `in`.koreatech.koin.domain.error.bus.BusErrorHandler import `in`.koreatech.koin.domain.model.article.articleNotiContent -import `in`.koreatech.koin.domain.model.bus.BusNode import `in`.koreatech.koin.domain.model.dining.Dining import `in`.koreatech.koin.domain.model.dining.DiningType import `in`.koreatech.koin.domain.model.store.StoreCategories import `in`.koreatech.koin.domain.repository.ArticleRepository -import `in`.koreatech.koin.domain.usecase.bus.timer.GetBusTimerUseCase import `in`.koreatech.koin.domain.usecase.dining.GetDiningUseCase import `in`.koreatech.koin.domain.usecase.store.GetStoreCategoriesUseCase import `in`.koreatech.koin.domain.usecase.user.ABTestUseCase @@ -27,14 +22,10 @@ import `in`.koreatech.koin.domain.util.TimeUtil import `in`.koreatech.koin.ui.main.state.ArticleMainState import `in`.koreatech.koin.ui.main.state.toContent import `in`.koreatech.koin.ui.main.state.toNoti -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -44,8 +35,6 @@ import javax.inject.Inject @HiltViewModel class MainActivityViewModel @Inject constructor( - private val getBusTimerUseCase: GetBusTimerUseCase, - private val busErrorHandler: BusErrorHandler, private val getDiningUseCase: GetDiningUseCase, private val getStoreCategoriesUseCase: GetStoreCategoriesUseCase, private val abTestUseCase: ABTestUseCase, @@ -53,8 +42,6 @@ class MainActivityViewModel @Inject constructor( ) : BaseViewModel() { private val _variableName = MutableLiveData() val variableName: LiveData get() = _variableName - private val _busNode = - MutableLiveData>(BusNode.Koreatech to BusNode.Terminal) val bannerABTestExperimentGroup = flow { abTestUseCase(Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentTitle).onSuccess { @@ -168,39 +155,12 @@ class MainActivityViewModel @Inject constructor( } } - val busTimer = liveData { - _busNode.asFlow() - .distinctUntilChanged() - .collectLatest { (departure, arrival) -> - _isLoading.value = false - try { - if (departure != arrival) { - getBusTimerUseCase(departure, arrival) - .conflate() - .collect { result -> - emit(result) - } - } - - } catch (_: CancellationException) { - } catch (e: Exception) { - _errorToast.value = busErrorHandler.handleGetBusRemainTimeError(e).message - } - } - } - fun checkKeywordNotiContent() { viewModelScope.launchWithLoading { articleRepository.saveKeywordNotiIndex().launchIn(viewModelScope) } } - fun switchBusNode() { - _busNode.value = _busNode.value?.let { (departure, arrival) -> - arrival to departure - } ?: (BusNode.Koreatech to BusNode.Terminal) - } - fun setDiningType(diningType: DiningType) { _selectedType.value = diningType } diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 2ce0a5930..6d8feadea 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -20,18 +20,19 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.navigation.NavigationView import dagger.hilt.android.AndroidEntryPoint +import `in`.koreatech.bus.BusSearchActivity +import `in`.koreatech.bus.BusTimetableActivity import `in`.koreatech.koin.BuildConfig import `in`.koreatech.koin.R import `in`.koreatech.koin.constant.URL 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.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.data.constant.URLConstant import `in`.koreatech.koin.domain.model.user.User import `in`.koreatech.koin.ui.article.ArticleActivity -import `in`.koreatech.koin.ui.bus.BusActivity import `in`.koreatech.koin.ui.dining.DiningActivity import `in`.koreatech.koin.ui.land.LandActivity import `in`.koreatech.koin.ui.login.LoginActivity @@ -74,7 +75,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), R.id.navi_item_setting, R.id.navi_item_login_or_logout, R.id.navi_item_store, - R.id.navi_item_bus, + R.id.navi_item_bus_timetable, + R.id.navi_item_bus_search, R.id.navi_item_dining, R.id.navi_item_operating_information, R.id.navi_item_timetable, @@ -89,7 +91,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), MenuState.Setting, MenuState.LoginOrLogout, MenuState.Store, - MenuState.Bus, + MenuState.BusTimetable, + MenuState.BusSearch, MenuState.Dining, MenuState.OperatingInfo, MenuState.Timetable, @@ -176,14 +179,6 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), ) } - MenuState.Bus -> { - EventLogger.logClickEvent( - EventAction.CAMPUS, - AnalyticsConstant.Label.HAMBURGER_BUS, - getString(R.string.bus) - ) - } - MenuState.Dining -> { EventLogger.logClickEvent( EventAction.CAMPUS, @@ -232,6 +227,20 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), ) } + MenuState.BusTimetable -> { + EventLogger.logCampusClickEvent( + AnalyticsConstant.Label.HAMBURGER, + "버스 시간표" + ) + } + + MenuState.BusSearch -> { + EventLogger.logCampusClickEvent( + AnalyticsConstant.Label.HAMBURGER, + "교통편 조회하기" + ) + } + else -> Unit } } @@ -274,7 +283,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), private fun initDrawerViewModel() = with(koinNavigationDrawerViewModel) { observeLiveData(menuEvent) { menuState -> when (menuState) { - MenuState.Bus -> goToBusActivity() + MenuState.BusTimetable -> goToBusTimetableActivity() + MenuState.BusSearch -> goToBusSearchActivity() MenuState.Dining -> goToDiningActivity() MenuState.OperatingInfo -> goToOperatingInfoActivity() MenuState.Land -> goToLandActivity() @@ -384,10 +394,6 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), koinNavigationDrawerViewModel.selectMenu(MenuState.OperatingInfo) } - R.id.navi_item_bus -> { - koinNavigationDrawerViewModel.selectMenu(MenuState.Bus) - } - R.id.navi_item_land -> { koinNavigationDrawerViewModel.selectMenu(MenuState.Land) } @@ -401,8 +407,6 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), open fun callDrawerItem(itemId: Int, bundle: Bundle?) { if (itemId == R.id.navi_item_store) { goToStoreActivity(bundle) - } else if (itemId == R.id.navi_item_bus) { - goToBusActivity(bundle) } } @@ -418,43 +422,40 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } } - private fun goToDiningActivity() { + private fun goToBusTimetableActivity() { if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, DiningActivity::class.java)) + goToActivityFinish(Intent(this, BusTimetableActivity::class.java)) } else { - startActivity(Intent(this, DiningActivity::class.java)) + startActivity(Intent(this, BusTimetableActivity::class.java)) } } - private fun goToOperatingInfoActivity() { + private fun goToBusSearchActivity() { if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, OperatingInfoActivity::class.java)) + goToActivityFinish(Intent(this, BusSearchActivity::class.java)) } else { - startActivity(Intent(this, OperatingInfoActivity::class.java)) + startActivity(Intent(this, BusSearchActivity::class.java)) } } - private fun goToBusActivity() { + private fun goToDiningActivity() { if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, BusActivity::class.java)) + goToActivityFinish(Intent(this, DiningActivity::class.java)) } else { - startActivity(Intent(this, BusActivity::class.java)) + startActivity(Intent(this, DiningActivity::class.java)) } } - private fun goToStoreActivity(bundle: Bundle?) { - val intent = Intent(this, StoreActivity::class.java) - intent.putExtras(bundle!!) - + private fun goToOperatingInfoActivity() { if (menuState != MenuState.Main) { - goToActivityFinish(intent) + goToActivityFinish(Intent(this, OperatingInfoActivity::class.java)) } else { - startActivity(intent) + startActivity(Intent(this, OperatingInfoActivity::class.java)) } } - private fun goToBusActivity(bundle: Bundle?) { - val intent = Intent(this, BusActivity::class.java) + private fun goToStoreActivity(bundle: Bundle?) { + val intent = Intent(this, StoreActivity::class.java) intent.putExtras(bundle!!) if (menuState != MenuState.Main) { diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/state/MenuState.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/state/MenuState.kt index ccacb9a61..f21c98e2f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/state/MenuState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/state/MenuState.kt @@ -5,7 +5,8 @@ sealed class MenuState { data object Setting: MenuState() data object LoginOrLogout: MenuState() data object Store: MenuState() - data object Bus: MenuState() + data object BusTimetable: MenuState() + data object BusSearch: MenuState() data object Dining: MenuState() data object OperatingInfo: MenuState() data object Timetable: MenuState() diff --git a/koin/src/main/java/in/koreatech/koin/ui/notification/NotificationActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/notification/NotificationActivity.kt index f61c3b1fe..105e9b181 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/notification/NotificationActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/notification/NotificationActivity.kt @@ -1,7 +1,6 @@ package `in`.koreatech.koin.ui.notification import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -14,16 +13,14 @@ 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.permission.checkNotificationPermission import `in`.koreatech.koin.core.activity.ActivityBase -import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.core.util.setAppBarButtonClickedListener import `in`.koreatech.koin.databinding.ActivityNotificationBinding import `in`.koreatech.koin.domain.model.notification.SubscribesDetailType import `in`.koreatech.koin.domain.model.notification.SubscribesType -import `in`.koreatech.koin.ui.article.ArticleActivity import `in`.koreatech.koin.ui.notification.viewmodel.NotificationUiState import `in`.koreatech.koin.ui.notification.viewmodel.NotificationViewModel import `in`.koreatech.koin.util.ext.withLoading diff --git a/koin/src/main/java/in/koreatech/koin/ui/scheme/SchemeActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/scheme/SchemeActivity.kt index 22774c3e2..4d07c3499 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/scheme/SchemeActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/scheme/SchemeActivity.kt @@ -9,7 +9,7 @@ import dagger.hilt.android.AndroidEntryPoint 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.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.navigation.Navigator import `in`.koreatech.koin.core.navigation.NavigatorType import `in`.koreatech.koin.core.navigation.SchemeType diff --git a/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt index 85db8bae0..2615705bc 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt @@ -14,7 +14,7 @@ import `in`.koreatech.koin.R 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.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.databinding.ActivitySignUpWithDetailInfoBinding import `in`.koreatech.koin.domain.error.signup.SignupAlreadySentEmailException import `in`.koreatech.koin.domain.model.user.Gender diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/CallBenefitStoreActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/CallBenefitStoreActivity.kt index 5efa509ca..6b4e73be5 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/CallBenefitStoreActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/CallBenefitStoreActivity.kt @@ -1,10 +1,8 @@ package `in`.koreatech.koin.ui.store.activity -import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper -import android.util.Log import androidx.activity.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -16,7 +14,7 @@ import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra 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.analytics.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityCallBenefitStoreMainBinding import `in`.koreatech.koin.domain.model.store.StoreSorter diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt index d79e69ab4..52d6d68f2 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.text.Editable -import android.util.Log import android.view.KeyEvent import android.view.MotionEvent import android.view.View @@ -29,14 +28,13 @@ import `in`.koreatech.koin.core.analytics.EventExtra import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.analytics.EventUtils import `in`.koreatech.koin.core.appbar.AppBarBase -import `in`.koreatech.koin.core.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.onboarding.OnboardingManager import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.core.viewpager.HorizontalMarginItemDecoration import `in`.koreatech.koin.databinding.StoreActivityMainBinding import `in`.koreatech.koin.domain.model.store.StoreEvent import `in`.koreatech.koin.domain.model.store.StoreSorter -import `in`.koreatech.koin.ui.businesssignup.BusinessSignUpCheckActivity import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerTimeActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import `in`.koreatech.koin.ui.store.adapter.StoreCategoriesRecyclerAdapter @@ -49,9 +47,7 @@ import `in`.koreatech.koin.ui.store.viewmodel.StoreViewModel import `in`.koreatech.koin.util.ext.dpToPx import `in`.koreatech.koin.util.ext.hideSoftKeyboard import `in`.koreatech.koin.util.ext.observeLiveData -import `in`.koreatech.koin.util.ext.showSoftKeyboard import `in`.koreatech.koin.util.ext.statusBarHeight -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt index f574060e9..87be80fac 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt @@ -24,7 +24,7 @@ import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra 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.analytics.AnalyticsConstant import `in`.koreatech.koin.core.dialog.ImageZoomableDialog import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.core.util.dataBinding diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreReviewReportActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreReviewReportActivity.kt index 131a86ccb..4edba3bbf 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreReviewReportActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreReviewReportActivity.kt @@ -1,13 +1,9 @@ package `in`.koreatech.koin.ui.store.activity -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.util.Log import android.view.MotionEvent -import android.view.View import android.view.inputmethod.InputMethodManager import androidx.activity.viewModels import androidx.core.content.ContextCompat @@ -20,17 +16,12 @@ 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.AppBarBase -import `in`.koreatech.koin.core.constant.AnalyticsConstant +import `in`.koreatech.koin.core.analytics.AnalyticsConstant import `in`.koreatech.koin.core.toast.ToastUtil -import `in`.koreatech.koin.databinding.ActivitySignupCompleteBinding import `in`.koreatech.koin.databinding.StoreActivityReportReviewBinding -import `in`.koreatech.koin.domain.state.signup.SignupContinuationState import `in`.koreatech.koin.domain.state.store.StoreReviewExceptionState import `in`.koreatech.koin.domain.state.store.StoreReviewState -import `in`.koreatech.koin.ui.store.contract.StoreDetailActivityContract import `in`.koreatech.koin.ui.store.viewmodel.StoreReviewReportViewModel -import `in`.koreatech.koin.ui.store.viewmodel.StoreViewModel -import `in`.koreatech.koin.util.SnackbarUtil import `in`.koreatech.koin.util.ext.observeLiveData import kotlinx.coroutines.launch diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/WriteReviewActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/WriteReviewActivity.kt index b716309c8..748eee028 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/WriteReviewActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/WriteReviewActivity.kt @@ -22,7 +22,7 @@ import `in`.koreatech.koin.core.activity.ActivityBase import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra 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.databinding.ActivityWriteReviewBinding import `in`.koreatech.koin.domain.model.store.Review import `in`.koreatech.koin.domain.model.store.StoreReviewContent diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreDetailImageViewpagerAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreDetailImageViewpagerAdapter.kt index e2d854c5a..9ced4c33a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreDetailImageViewpagerAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreDetailImageViewpagerAdapter.kt @@ -7,10 +7,6 @@ import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide 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.dialog.ImageZoomableDialog class StoreDetailImageViewpagerAdapter( private val images: List?, diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/contract/StoreDetailActivityContract.kt b/koin/src/main/java/in/koreatech/koin/ui/store/contract/StoreDetailActivityContract.kt index 836c75d67..5f2116815 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/contract/StoreDetailActivityContract.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/contract/StoreDetailActivityContract.kt @@ -7,7 +7,7 @@ import androidx.activity.result.contract.ActivityResultContract import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra 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.ui.store.activity.StoreActivity import `in`.koreatech.koin.ui.store.activity.StoreDetailActivity diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailEventFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailEventFragment.kt index cbc70135a..9a59fac39 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailEventFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailEventFragment.kt @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.RecyclerView 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.databinding.FragmentStoreDetailEventBinding import `in`.koreatech.koin.domain.model.store.StoreDetailScrollType import `in`.koreatech.koin.ui.store.adapter.StoreDetailEventRecyclerAdapter diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailMenuFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailMenuFragment.kt index deef23424..4d485a439 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailMenuFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailMenuFragment.kt @@ -12,7 +12,7 @@ import androidx.fragment.app.activityViewModels 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.databinding.FragmentStoreDetailMenuBinding import `in`.koreatech.koin.domain.model.store.ShopMenus import `in`.koreatech.koin.domain.model.store.StoreDetailScrollType diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailReviewFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailReviewFragment.kt index 263e2c5f2..451b47ecf 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailReviewFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/fragment/StoreDetailReviewFragment.kt @@ -17,7 +17,7 @@ import `in`.koreatech.koin.R import `in`.koreatech.koin.contract.LoginContract 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.toast.ToastUtil import `in`.koreatech.koin.databinding.FragmentStoreDetailReviewBinding import `in`.koreatech.koin.domain.model.store.ReviewFilterEnum diff --git a/koin/src/main/java/in/koreatech/koin/util/ext/SpinnerExtension.kt b/koin/src/main/java/in/koreatech/koin/util/ext/SpinnerExtension.kt index 589910e32..1069aac56 100644 --- a/koin/src/main/java/in/koreatech/koin/util/ext/SpinnerExtension.kt +++ b/koin/src/main/java/in/koreatech/koin/util/ext/SpinnerExtension.kt @@ -9,13 +9,13 @@ import android.widget.Spinner inline fun AdapterView.setOnItemSelectedListener( crossinline nothingSelected: (AdapterView<*>) -> Unit = {}, crossinline itemSelected: ( - parent: AdapterView<*>, - view: View, + parent: AdapterView<*>?, + view: View?, position: Int, id: Long) -> Unit ) { this.onItemSelectedListener = object : OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { itemSelected(parent, view, position, id) } diff --git a/koin/src/main/res/layout/activity_main.xml b/koin/src/main/res/layout/activity_main.xml index 13aab2db2..ad7067c06 100644 --- a/koin/src/main/res/layout/activity_main.xml +++ b/koin/src/main/res/layout/activity_main.xml @@ -130,32 +130,10 @@ - - - - - - - - - - - - - - - - - - - - \ 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 @@ - - - - - - - - - -