From c610f27943586a6e521d2329e22632af7f1a53d6 Mon Sep 17 00:00:00 2001 From: Wooyoung Myung Date: Wed, 19 Jun 2024 14:04:19 +0900 Subject: [PATCH] =?UTF-8?q?kuring-168=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=84=20compose=EB=A1=9C=20migrate=20(#235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [추가] 메인 화면의 navigation route 정의 * [추가] 메인 화면의 navigation bar 정의 * [추가] compose type-safe navigation 라이브러리 추가 * [수정] 메인 화면 route에 @Serializable 어노테이션 추가 * [수정] KuringSwitch에서 라이브러리 변경에 대응 * [추가] MainScreen composable 구현 * [수정] MainActivity에서 XML 대신 Compose MainScreen을 호출 * [제거] 사용하지 않는 view 코드 제거 * Fix transition issue * [수정] KuringSwitch의 미구현 인터페이스 구현 --------- Co-authored-by: HyunWoo Lee --- .../buildlogic/primitive/KotlinPlugin.kt | 3 + build.gradle.kts | 1 + .../designsystem/components/KuringSwitch.kt | 15 +- .../ku_stacks/ku_ring/main/MainActivity.kt | 81 +++---- .../ku_ring/main/MainPagerAdapter.kt | 23 -- .../com/ku_stacks/ku_ring/main/MainScreen.kt | 215 ++++++++++++++++++ .../ku_ring/main/MainScreenNavigationBar.kt | 154 +++++++++++++ .../ku_stacks/ku_ring/main/MainScreenRoute.kt | 52 +++++ .../src/main/res/layout/activity_main.xml | 28 --- feature/main/src/main/res/values/strings.xml | 3 + gradle/libs.versions.toml | 8 +- 11 files changed, 472 insertions(+), 111 deletions(-) delete mode 100644 feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainPagerAdapter.kt create mode 100644 feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreen.kt create mode 100644 feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenNavigationBar.kt create mode 100644 feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenRoute.kt delete mode 100644 feature/main/src/main/res/layout/activity_main.xml diff --git a/build-logic/src/main/kotlin/com/ku_stacks/ku_ring/buildlogic/primitive/KotlinPlugin.kt b/build-logic/src/main/kotlin/com/ku_stacks/ku_ring/buildlogic/primitive/KotlinPlugin.kt index cfd9c69b6..48c1be231 100644 --- a/build-logic/src/main/kotlin/com/ku_stacks/ku_ring/buildlogic/primitive/KotlinPlugin.kt +++ b/build-logic/src/main/kotlin/com/ku_stacks/ku_ring/buildlogic/primitive/KotlinPlugin.kt @@ -18,6 +18,7 @@ class KotlinPlugin : Plugin { with(target) { with(plugins) { apply("kotlin-android") + apply("kotlinx-serialization") } tasks.withType { @@ -36,6 +37,8 @@ class KotlinPlugin : Plugin { implementation(libs.library("kotlinx-coroutines-android")) implementation(libs.library("kotlinx-coroutines-reactive")) implementation(libs.library("kotlinx-coroutines-rxjava3")) + + implementation(libs.library("kotlinx-serialization")) } } } diff --git a/build.gradle.kts b/build.gradle.kts index b80cce5a6..be4d09e93 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,7 @@ buildscript { classpath(libs.google.services) classpath(libs.firebase.crashlytics.gradle) classpath(libs.oss.licenses) + classpath(libs.kotlinx.serialization.plugin) } } diff --git a/common/designsystem/src/main/java/com/ku_stacks/ku_ring/designsystem/components/KuringSwitch.kt b/common/designsystem/src/main/java/com/ku_stacks/ku_ring/designsystem/components/KuringSwitch.kt index c001f5323..6f4d0c607 100644 --- a/common/designsystem/src/main/java/com/ku_stacks/ku_ring/designsystem/components/KuringSwitch.kt +++ b/common/designsystem/src/main/java/com/ku_stacks/ku_ring/designsystem/components/KuringSwitch.kt @@ -1,7 +1,11 @@ package com.ku_stacks.ku_ring.designsystem.components import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.AnimationVector +import androidx.compose.animation.core.DecayAnimationSpec import androidx.compose.animation.core.TweenSpec +import androidx.compose.animation.core.TwoWayConverter +import androidx.compose.animation.core.VectorizedDecayAnimationSpec import androidx.compose.foundation.Canvas import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -102,13 +106,18 @@ fun KuringSwitch( val anchoredDraggableState = remember(maxBound, switchVelocityThresholdPx) { AnchoredDraggableState( initialValue = checked, - animationSpec = AnimationSpec, anchors = DraggableAnchors { false at minBound true at maxBound }, - positionalThreshold = { distance -> distance * SwitchPositionalThreshold }, - velocityThreshold = { switchVelocityThresholdPx } + positionalThreshold = { distance: Float -> distance * SwitchPositionalThreshold }, + velocityThreshold = { switchVelocityThresholdPx }, + snapAnimationSpec = AnimationSpec, + decayAnimationSpec = object : DecayAnimationSpec { + override fun vectorize(typeConverter: TwoWayConverter): VectorizedDecayAnimationSpec { + return vectorize(typeConverter) + } + }, ) } val currentOnCheckedChange by rememberUpdatedState(onCheckedChange) diff --git a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainActivity.kt b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainActivity.kt index f989c7d7c..1bebdf1c7 100644 --- a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainActivity.kt +++ b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainActivity.kt @@ -4,11 +4,18 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem +import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.viewpager2.widget.ViewPager2 +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.navigation.compose.rememberNavController +import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme import com.ku_stacks.ku_ring.domain.WebViewNotice -import com.ku_stacks.ku_ring.main.databinding.ActivityMainBinding import com.ku_stacks.ku_ring.ui_util.KuringNavigator import com.ku_stacks.ku_ring.ui_util.showToast import dagger.hilt.android.AndroidEntryPoint @@ -17,23 +24,9 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { - private lateinit var binding: ActivityMainBinding - @Inject lateinit var navigator: KuringNavigator - private val pageChangeCallback = object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - binding.mainBottomNavigation.selectedItemId = when (position) { - 0 -> R.id.notice_screen - 1 -> R.id.archive_screen - 2 -> R.id.campus_map_screen - 3 -> R.id.setting_screen - else -> throw IllegalStateException("no such main viewpager position") - } - } - } - private var backPressedTime = 0L override fun onNewIntent(intent: Intent) { @@ -51,40 +44,23 @@ class MainActivity : AppCompatActivity() { navToNoticeActivity(webViewNotice) } - setupBinding() - setupView() - } - - private fun setupBinding() { - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - } - - private fun setupView() { - val pagerAdapter = MainPagerAdapter(supportFragmentManager, lifecycle) - - binding.mainViewPager.apply { - adapter = pagerAdapter - registerOnPageChangeCallback(pageChangeCallback) - isUserInputEnabled = false - offscreenPageLimit = pagerAdapter.itemCount - } - - binding.mainBottomNavigation.setOnItemSelectedListener { - navigationSelected(it) - } - } - - private fun navigationSelected(item: MenuItem): Boolean { - when (item.setChecked(true).itemId) { - R.id.notice_screen -> binding.mainViewPager.setCurrentItem(0, false) - R.id.archive_screen -> binding.mainViewPager.setCurrentItem(1, false) - R.id.campus_map_screen -> binding.mainViewPager.setCurrentItem(2, false) - R.id.setting_screen -> binding.mainViewPager.setCurrentItem(3, false) - else -> return false + setContent { + KuringTheme { + var currentRoute: MainScreenRoute by remember { mutableStateOf(MainScreenRoute.Notice) } + val navController = rememberNavController() + MainScreen( + navController = navController, + currentRoute = currentRoute, + onNavigateToRoute = { + if (currentRoute != it) { + currentRoute = it + navController.navigate(it) + } + }, + modifier = Modifier.fillMaxSize().background(KuringTheme.colors.background), + ) + } } - - return true } private fun navToNoticeActivity(webViewNotice: WebViewNotice) { @@ -100,11 +76,6 @@ class MainActivity : AppCompatActivity() { } } - override fun onDestroy() { - super.onDestroy() - binding.mainViewPager.unregisterOnPageChangeCallback(pageChangeCallback) - } - companion object { fun createIntent(context: Context) = Intent(context, MainActivity::class.java) diff --git a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainPagerAdapter.kt b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainPagerAdapter.kt deleted file mode 100644 index a096d2805..000000000 --- a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainPagerAdapter.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.ku_stacks.ku_ring.main - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.ku_stacks.ku_ring.main.archive.ArchiveFragment -import com.ku_stacks.ku_ring.main.campus_onboarding.CampusFragment -import com.ku_stacks.ku_ring.main.notice.NoticesParentFragment -import com.ku_stacks.ku_ring.main.setting.SettingFragment - -class MainPagerAdapter(fm: FragmentManager, lc: Lifecycle) : FragmentStateAdapter(fm, lc) { - private val items = arrayOf( - { NoticesParentFragment() }, - { ArchiveFragment() }, - { CampusFragment() }, - { SettingFragment() } - ) - - override fun getItemCount() = items.size - - override fun createFragment(position: Int): Fragment = items[position].invoke() -} diff --git a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreen.kt b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreen.kt new file mode 100644 index 000000000..259b43a12 --- /dev/null +++ b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreen.kt @@ -0,0 +1,215 @@ +package com.ku_stacks.ku_ring.main + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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 androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme +import com.ku_stacks.ku_ring.main.archive.compose.ArchiveScreen +import com.ku_stacks.ku_ring.main.notice.compose.NoticeScreen +import com.ku_stacks.ku_ring.main.setting.SettingViewModel +import com.ku_stacks.ku_ring.main.setting.compose.OpenSourceActivity +import com.ku_stacks.ku_ring.main.setting.compose.inner_screen.SettingScreen +import com.ku_stacks.ku_ring.thirdparty.compose.KuringCompositionLocalProvider +import com.ku_stacks.ku_ring.thirdparty.di.LocalNavigator +import com.ku_stacks.ku_ring.ui_util.KuringNavigator +import com.ku_stacks.ku_ring.util.findActivity + +@Composable +fun MainScreen( + navController: NavHostController, + currentRoute: MainScreenRoute, + onNavigateToRoute: (MainScreenRoute) -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + bottomBar = { + MainScreenNavigationBar( + currentRoute = currentRoute, + onNavigationItemClick = { onNavigateToRoute(it) }, + modifier = Modifier.fillMaxWidth(), + ) + }, + modifier = modifier, + ) { + KuringCompositionLocalProvider { + val navigator = LocalNavigator.current + val activity = + LocalContext.current.findActivity() ?: return@KuringCompositionLocalProvider + + NavHost( + navController = navController, + startDestination = MainScreenRoute.Notice, + modifier = Modifier.padding(it).fillMaxSize(), + enterTransition = { + val initialRoute = MainScreenRoute.of(initialState.destination.route.orEmpty()) + val targetRoute = MainScreenRoute.of(targetState.destination.route.orEmpty()) + val enterDirection = + slideDirection( + initialRoute = initialRoute, + targetRoute = targetRoute, + ) + slideIntoContainer(enterDirection) + }, + exitTransition = { + val initialRoute = MainScreenRoute.of(initialState.destination.route.orEmpty()) + val targetRoute = MainScreenRoute.of(targetState.destination.route.orEmpty()) + val enterDirection = + slideDirection( + initialRoute = initialRoute, + targetRoute = targetRoute, + ) + slideOutOfContainer(enterDirection) + }, + ) { + mainScreenNavGraph( + navigator = navigator, + activity = activity, + ) + } + } + } +} + +private fun MainScreenRoute.screenOrder() = + when (this) { + is MainScreenRoute.Notice -> 0 + is MainScreenRoute.Archive -> 1 + is MainScreenRoute.CampusMap -> 2 + is MainScreenRoute.Settings -> 3 + } + +private fun slideDirection( + initialRoute: MainScreenRoute, + targetRoute: MainScreenRoute, +) = if (initialRoute.screenOrder() > targetRoute.screenOrder()) { + AnimatedContentTransitionScope.SlideDirection.Right +} else { + AnimatedContentTransitionScope.SlideDirection.Left +} + +fun NavGraphBuilder.mainScreenNavGraph( + navigator: KuringNavigator, + activity: Activity, +) { + composable { + NoticeScreen( + onSearchIconClick = { + navigator.navigateToSearch(activity) + }, + onNotificationIconClick = { + navigator.navigateToEditSubscription(activity) + }, + onNoticeClick = { + navigator.navigateToNoticeWeb(activity, it) + }, + onNavigateToEditDepartment = { + navigator.navigateToEditSubscribedDepartment(activity) + }, + modifier = + Modifier + .background(KuringTheme.colors.background) + .fillMaxSize(), + ) + } + composable { + ArchiveScreen( + modifier = + Modifier + .background(KuringTheme.colors.background) + .fillMaxSize(), + ) + } + composable { + Box( + modifier = + Modifier + .background(KuringTheme.colors.background) + .fillMaxSize(), + ) { + Text( + text = "TODO: Compose migration하기", + color = KuringTheme.colors.textBody, + ) + } + } + composable { + // TODO by mwy3055: SettingScreen 내부도 navigation으로 migrate해야 함 + val viewModel = hiltViewModel() + val isExtNotificationAllowed by viewModel.isExtNotificationAllowed.collectAsStateWithLifecycle() + SettingScreen( + onNavigateToEditSubscription = { navigator.navigateToEditSubscription(activity) }, + isExtNotificationEnabled = isExtNotificationAllowed, + onExtNotificationEnabledToggle = viewModel::setExtNotificationAllowed, + onNavigateToUpdateLog = { + activity.startWebView(navigator, R.string.notion_new_contents_url) + }, + onNavigateToKuringTeam = { + activity.startWebView(navigator, R.string.notion_kuring_team_url) + }, + onNavigateToPrivacyPolicy = { + activity.startWebView(navigator, R.string.notion_privacy_policy_url) + }, + onNavigateToServiceTerms = { + activity.startWebView(navigator, R.string.notion_terms_of_service_url) + }, + onNavigateToOpenSources = { OpenSourceActivity.start(activity) }, + onNavigateToKuringInstagram = { activity.navigateToKuringInstagram() }, + onNavigateToFeedback = { navigator.navigateToFeedback(activity) }, + modifier = + Modifier + .background(KuringTheme.colors.background) + .fillMaxWidth() + .wrapContentHeight(), + ) + } +} + +private fun Activity.startWebView( + navigator: KuringNavigator, + @StringRes urlId: Int, +) { + val url = getString(urlId) + navigator.navigateToNotionView(this, url) + this.overridePendingTransition( + R.anim.anim_slide_right_enter, + R.anim.anim_stay_exit, + ) +} + +private fun Activity.navigateToKuringInstagram() { + val intent = getInstagramIntent() + startActivity(intent) +} + +private fun Activity.getInstagramIntent(): Intent { + val packageName = getString(R.string.instagram_package) + val appScheme = getString(R.string.instagram_app_scheme) + val webScheme = getString(R.string.instagram_web_scheme) + return try { + packageManager.getPackageInfo(packageName, 0) + Intent(Intent.ACTION_VIEW, Uri.parse(appScheme)) + } catch (e: PackageManager.NameNotFoundException) { + Intent(Intent.ACTION_VIEW, Uri.parse(webScheme)) + } +} diff --git a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenNavigationBar.kt b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenNavigationBar.kt new file mode 100644 index 000000000..012709dc7 --- /dev/null +++ b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenNavigationBar.kt @@ -0,0 +1,154 @@ +package com.ku_stacks.ku_ring.main + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.animation.Crossfade +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.remember +import androidx.compose.runtime.setValue +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.semantics.Role +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ku_stacks.ku_ring.designsystem.components.LightAndDarkPreview +import com.ku_stacks.ku_ring.designsystem.kuringtheme.KuringTheme +import com.ku_stacks.ku_ring.designsystem.kuringtheme.values.Pretendard + +@Composable +internal fun MainScreenNavigationBar( + currentRoute: MainScreenRoute, + onNavigationItemClick: (MainScreenRoute) -> Unit, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier.background(KuringTheme.colors.background)) { + MainScreenRoute.entries.forEach { route -> + val navigationItem = MainScreenNavigationBarItem.get(route) + val label = stringResource(id = navigationItem.labelId) + + MainScreenNavigationItem( + item = navigationItem, + isSelected = route == currentRoute, + modifier = Modifier.weight(1f) + .clickable( + role = Role.Button, + onClickLabel = stringResource( + id = R.string.main_bottom_navigation_item, + label + ), + onClick = { onNavigationItemClick(route) }, + ).padding(vertical = 11.dp), + ) + } + } +} + +@Composable +private fun MainScreenNavigationItem( + item: MainScreenNavigationBarItem, + isSelected: Boolean, + modifier: Modifier = Modifier, +) { + val label = stringResource(id = item.labelId) + val contentColor by animateColorAsState( + targetValue = if (isSelected) KuringTheme.colors.gray600 else KuringTheme.colors.textCaption1, + label = "MainScreenNavigationItem labelColor", + ) + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(5.25.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Crossfade( + targetState = isSelected, + label = "$label icon", + ) { selected -> + Icon( + imageVector = ImageVector.vectorResource(id = if (selected) item.selectedIconId else item.unselectedIconId), + contentDescription = null, + tint = contentColor, + ) + } + Text( + text = label, + style = TextStyle( + fontSize = 10.sp, + lineHeight = 16.3.sp, + fontFamily = Pretendard, + fontWeight = FontWeight(500), + color = contentColor, + ) + ) + } +} + +data class MainScreenNavigationBarItem( + @DrawableRes val selectedIconId: Int, + @DrawableRes val unselectedIconId: Int, + @StringRes val labelId: Int, +) { + companion object { + fun get(destination: MainScreenRoute): MainScreenNavigationBarItem { + return when (destination) { + MainScreenRoute.Notice -> MainScreenNavigationBarItem( + selectedIconId = R.drawable.ic_list_fill_v2, + unselectedIconId = R.drawable.ic_list_v2, + labelId = R.string.navigator_notice_screen, + ) + + MainScreenRoute.Archive -> MainScreenNavigationBarItem( + selectedIconId = R.drawable.ic_archive_fill_v2, + unselectedIconId = R.drawable.ic_archive_v2, + labelId = R.string.navigator_archive_screen, + ) + + MainScreenRoute.CampusMap -> MainScreenNavigationBarItem( + selectedIconId = R.drawable.ic_map_pin_fill_v2, + unselectedIconId = R.drawable.ic_map_pin_v2, + labelId = R.string.navigator_campus_map_screen, + ) + + MainScreenRoute.Settings -> MainScreenNavigationBarItem( + selectedIconId = R.drawable.ic_more_horizontal_fill_v2, + unselectedIconId = R.drawable.ic_more_horizontal_v2, + labelId = R.string.navigator_setting_screen, + ) + } + } + } +} + +@LightAndDarkPreview +@Composable +private fun MainScreenNavigationBarPreview() { + var selectedDestination: MainScreenRoute by remember { + mutableStateOf( + MainScreenRoute.Notice + ) + } + KuringTheme { + MainScreenNavigationBar( + currentRoute = selectedDestination, + onNavigationItemClick = { selectedDestination = it }, + modifier = Modifier.background(KuringTheme.colors.background).fillMaxWidth(), + ) + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenRoute.kt b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenRoute.kt new file mode 100644 index 000000000..30e27629f --- /dev/null +++ b/feature/main/src/main/java/com/ku_stacks/ku_ring/main/MainScreenRoute.kt @@ -0,0 +1,52 @@ +package com.ku_stacks.ku_ring.main + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MainScreenRoute { + val route: String + + @Serializable + data object Notice : MainScreenRoute { + override val route: String + get() = + this::class.java.canonicalName + ?.toString() + .toString() + } + + @Serializable + data object Archive : MainScreenRoute { + override val route: String + get() = + this::class.java.canonicalName + ?.toString() + .toString() + } + + @Serializable + data object CampusMap : MainScreenRoute { + override val route: String + get() = + this::class.java.canonicalName + ?.toString() + .toString() + } + + @Serializable + data object Settings : MainScreenRoute { + override val route: String + get() = + this::class.java.canonicalName + ?.toString() + .toString() + } + + companion object { + val entries = listOf(Notice, Archive, CampusMap, Settings) + + fun of(route: String): MainScreenRoute = + entries.firstOrNull { it.route == route } + ?: throw IllegalArgumentException("Unknown route: $route") + } +} diff --git a/feature/main/src/main/res/layout/activity_main.xml b/feature/main/src/main/res/layout/activity_main.xml deleted file mode 100644 index fd5902cee..000000000 --- a/feature/main/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/feature/main/src/main/res/values/strings.xml b/feature/main/src/main/res/values/strings.xml index b0551a3da..a9452b729 100644 --- a/feature/main/src/main/res/values/strings.xml +++ b/feature/main/src/main/res/values/strings.xml @@ -3,6 +3,9 @@ 더보기 + + %s 화면으로 이동하기 + 검색 결과 공지 교수명 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8df30c028..67a1de871 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ firebase = '33.1.0' work = '2.9.0' desugarJdk = "2.0.4" crashlytics = "3.0.1" -androidx-navigation = "2.7.7" +androidx-navigation = "2.8.0-beta03" androidx-espresso = "3.5.1" [libraries] @@ -43,6 +43,7 @@ hilt-android-gradle = { module = 'com.google.dagger:hilt-android-gradle-plugin', google-services = { module = 'com.google.gms:google-services', version.ref = 'google-services' } firebase-crashlytics-gradle = { module = 'com.google.firebase:firebase-crashlytics-gradle', version.ref = 'firebase-gradle' } oss-licenses = { module = 'com.google.android.gms:oss-licenses-plugin', version.ref = 'oss-licenses-version' } +kotlinx-serialization-plugin = { module = 'org.jetbrains.kotlin:kotlin-serialization', version.ref = 'kotlin' } # Android android-material = 'com.google.android.material:material:1.12.0' @@ -94,7 +95,7 @@ androidx-lifecycle-viewmodel-compose = { module = 'androidx.lifecycle:lifecycle- androidx-lifecycle-runtime-compose = { module = 'androidx.lifecycle:lifecycle-runtime-compose', version.ref = 'androidx-lifecycle' } androidx-paging-compose = { module = 'androidx.paging:paging-compose', version.ref = 'paging' } hilt-navigation-compose = 'androidx.hilt:hilt-navigation-compose:1.2.0' -androidx-navigation-compose = 'androidx.navigation:navigation-compose:2.7.7' +androidx-navigation-compose = { module = 'androidx.navigation:navigation-compose', version.ref = 'androidx-navigation' } lottie-compose = 'com.airbnb.android:lottie-compose:6.4.1' # Coil @@ -110,6 +111,9 @@ kotlinx-coroutines-rxjava3 = { module = 'org.jetbrains.kotlinx:kotlinx-coroutine # Test는 bundle로 묶지 말기 (필요한 모듈에서만 별도로 선언) kotlinx-coroutines-test = { module = 'org.jetbrains.kotlinx:kotlinx-coroutines-test', version.ref = 'kotlinx-coroutines' } +# Kotlin Serialization +kotlinx-serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0" + # Hilt androidx-hilt-compiler = 'androidx.hilt:hilt-compiler:1.2.0' hilt-android = { module = 'com.google.dagger:hilt-android', version.ref = 'hilt' }