diff --git a/app/src/main/java/com/runnect/runnect/di/NavigationModule.kt b/app/src/main/java/com/runnect/runnect/di/NavigationModule.kt new file mode 100644 index 00000000..42d18695 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/di/NavigationModule.kt @@ -0,0 +1,17 @@ +package com.runnect.runnect.di + +import com.runnect.runnect.presentation.navigation.Navigator +import com.runnect.runnect.presentation.navigation.NavigatorImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface NavigationModule { + @Singleton + @Binds + fun bindNavigator(navigatorImpl: NavigatorImpl): Navigator +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt index c5fadbee..19e6e0ae 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt @@ -14,9 +14,12 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityMainBinding import com.runnect.runnect.presentation.event.VisitorModeManager +import com.runnect.runnect.presentation.navigation.EXTRA_MAIN_TAB +import com.runnect.runnect.presentation.navigation.MainTab import com.runnect.runnect.util.analytics.Analytics import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_VIEW_HOME +import com.runnect.runnect.util.extension.getCompatibleSerializableExtra import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -25,41 +28,46 @@ class MainActivity : BindingActivity(R.layout.activity_main @Inject lateinit var visitorModeManager: VisitorModeManager - private var isChangeToStorage: Boolean = false - private var isChangeToDiscover: Boolean = false - private var fragmentReplacementDirection: String? = null private lateinit var viewPagerAdapter: MainPager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + Analytics.logClickedItemEvent(EVENT_VIEW_HOME) initRemoteConfig() - checkIntentValue() - initView() + initView(resolveLandingTab(intent)) addListener() } - private fun checkIntentValue() { - fragmentReplacementDirection = intent.getStringExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION) - - when (fragmentReplacementDirection) { - "fromDrawCourse", "fromDeleteMyDrawDetail", "fromMyDrawDetail" -> isChangeToStorage = true - "fromMyScrap", "fromCourseDetail" -> isChangeToDiscover = true - } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + applyLandingTab(resolveLandingTab(intent)) } - private fun initView() { + private fun resolveLandingTab(intent: Intent): MainTab? = + intent.getCompatibleSerializableExtra(EXTRA_MAIN_TAB) + + private fun initView(landingTab: MainTab?) { setupViewPager() - val selectedPosition = when { - isChangeToStorage -> 1.also { isChangeToStorage = false } - isChangeToDiscover -> 2.also { isChangeToDiscover = false } - else -> 0 - } + val selectedPosition = landingTab?.toPagerPosition() ?: 0 binding.vpMain.currentItem = selectedPosition updateBottomNavigationSelection(selectedPosition) } + private fun applyLandingTab(landingTab: MainTab?) { + val position = landingTab?.toPagerPosition() ?: return + binding.vpMain.currentItem = position + updateBottomNavigationSelection(position) + } + + private fun MainTab.toPagerPosition(): Int = when (this) { + MainTab.DRAWING -> 0 + MainTab.STORAGE -> 1 + MainTab.DISCOVER -> 2 + MainTab.MY_PAGE -> 3 + } + private fun setupViewPager() { viewPagerAdapter = MainPager(this) binding.vpMain.apply { @@ -165,6 +173,5 @@ class MainActivity : BindingActivity(R.layout.activity_main companion object { const val REMOTE_CONFIG_FETCH_INTERVAL_SECONDS = 3600L - const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" } } diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt index d76d77ce..32d8e1e8 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt @@ -22,11 +22,13 @@ import com.runnect.runnect.data.dto.CourseData import com.runnect.runnect.databinding.ActivityCourseDetailBinding import com.runnect.runnect.domain.entity.CourseDetail import com.runnect.runnect.domain.entity.EditableCourseDetail -import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity import com.runnect.runnect.presentation.event.ScreenRefreshEvent import com.runnect.runnect.presentation.event.ScreenRefreshEventBus import com.runnect.runnect.presentation.event.VisitorModeManager +import com.runnect.runnect.presentation.navigation.MainTab +import com.runnect.runnect.presentation.navigation.NavigationMode +import com.runnect.runnect.presentation.navigation.Navigator import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER_SEARCH import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_STORAGE_SCRAP @@ -76,6 +78,9 @@ class CourseDetailActivity : @Inject lateinit var screenRefreshEventBus: ScreenRefreshEventBus + @Inject + lateinit var navigator: Navigator + private val viewModel: CourseDetailViewModel by viewModels() private val isVisitorMode: Boolean get() = visitorModeManager.isVisitorMode private var isFromDeepLink: Boolean = false @@ -433,11 +438,7 @@ class CourseDetailActivity : } private fun navigateToMainScreen() { - Intent(this@CourseDetailActivity, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, EXTRA_FROM_COURSE_DETAIL) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - startActivity(this) - } + navigator.navigateToMain(this, MainTab.DISCOVER, NavigationMode.NEW_TASK_CLEAR_TASK) applyScreenExitAnimation() } @@ -625,8 +626,6 @@ class CourseDetailActivity : private const val EXTRA_ROOT_SCREEN = "rootScreen" private const val EXTRA_PUBLIC_COURSE_ID = "publicCourseId" private const val EXTRA_COURSE_DATA = "CourseData" - private const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" - private const val EXTRA_FROM_COURSE_DETAIL = "fromCourseDetail" private const val EXTRA_COURSE_USER_ID = "courseUserId" private const val TAG_MY_UPLOAD_COURSE_DELETE_DIALOG = "MY_UPLOAD_COURSE_DELETE_DIALOG" diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index f75df882..12fa9e20 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -43,9 +43,11 @@ import com.runnect.runnect.data.dto.UploadLatLng import com.runnect.runnect.databinding.ActivityDrawBinding import com.runnect.runnect.databinding.BottomsheetRequireCourseNameBinding import com.runnect.runnect.databinding.CustomDialogMakeCourseBinding -import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity import com.runnect.runnect.presentation.event.VisitorModeManager +import com.runnect.runnect.presentation.navigation.MainTab +import com.runnect.runnect.presentation.navigation.NavigationMode +import com.runnect.runnect.presentation.navigation.Navigator import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.DepartureSetMode import com.runnect.runnect.util.analytics.Analytics @@ -74,6 +76,9 @@ class DrawActivity : BindingActivity(R.layout.activity_draw @Inject lateinit var visitorModeManager: VisitorModeManager + @Inject + lateinit var navigator: Navigator + private lateinit var locationSource: FusedLocationSource private lateinit var currentLocation: LatLng private lateinit var fusedLocation: FusedLocationProviderClient // 현재 위치 반환 객체 변수 @@ -555,11 +560,7 @@ class DrawActivity : BindingActivity(R.layout.activity_draw btnStorage.setOnClickListener { Analytics.logClickedItemEvent(EventName.EVENT_CLICK_STORED_AFTER_COURSE_COMPLETE) - val intent = Intent(this@DrawActivity, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, "fromDrawCourse") - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } - startActivity(intent) + navigator.navigateToMain(this@DrawActivity, MainTab.STORAGE, NavigationMode.CLEAR_TOP) dialog.dismiss() } } @@ -860,7 +861,6 @@ class DrawActivity : BindingActivity(R.layout.activity_draw const val LEAST_CONDITION_CREATE_PATH = 2 const val EXTRA_SEARCH_RESULT = "searchResult" const val EXTRA_COURSE_DATA = "CourseData" - const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" const val CUSTOM_DEPARTURE = "내가 설정한 출발지" const val NOTIFY_LIMIT_MARKER_NUM = "마커는 20개까지 생성 가능합니다" diff --git a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt index 95d63ba5..5e47a997 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt @@ -16,7 +16,10 @@ import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.data.dto.RunToEndRunData import com.runnect.runnect.data.dto.request.RequestPostRunningHistory import com.runnect.runnect.databinding.ActivityEndRunBinding -import com.runnect.runnect.presentation.MainActivity +import com.runnect.runnect.presentation.navigation.MainTab +import com.runnect.runnect.presentation.navigation.NavigationMode +import com.runnect.runnect.presentation.navigation.Navigator +import com.runnect.runnect.presentation.navigation.mapDataFromToMainTab import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics import com.runnect.runnect.util.analytics.EventName @@ -29,10 +32,14 @@ import com.runnect.runnect.util.extension.round import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber import java.text.SimpleDateFormat +import javax.inject.Inject import kotlin.math.roundToInt @AndroidEntryPoint class EndRunActivity: BindingActivity(R.layout.activity_end_run) { + @Inject + lateinit var navigator: Navigator + val viewModel: EndRunViewModel by viewModels() val currentTime: Long = System.currentTimeMillis() @@ -82,15 +89,19 @@ class EndRunActivity: BindingActivity(R.layout.activity_e private fun backBtn() { binding.imgBtnBack.setOnClickListener { Analytics.logClickedItemEvent(EVENT_CLICK_BACK_RUNNING_TRACKING) - val intent = Intent(this, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, viewModel.dataFrom.value) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } - startActivity(intent) + navigateToMain() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } } + private fun navigateToMain() { + navigator.navigateToMain( + this, + mapDataFromToMainTab(viewModel.dataFrom.value), + NavigationMode.CLEAR_TOP + ) + } + private fun editTextController() { binding.etTitleCourse.addTextChangedListener { if (!binding.etTitleCourse.text.isNullOrEmpty()) { @@ -181,10 +192,7 @@ class EndRunActivity: BindingActivity(R.layout.activity_e pace = "0:$paceMinute:$paceSecond" //평균 페이스 표기법에서는 '시간'을 안 쓰지만 서버에서 요구하는 형식에 맞춰주기 위해 앞에 "0:"을 붙어야 함 ) ) - val intent = Intent(this, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } - startActivity(intent) + navigator.navigateToMain(this, mode = NavigationMode.CLEAR_TOP) } } @@ -227,11 +235,7 @@ class EndRunActivity: BindingActivity(R.layout.activity_e override fun onBackPressed() { - val intent = Intent(this, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, viewModel.dataFrom.value) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } - startActivity(intent) + navigateToMain() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } @@ -253,7 +257,6 @@ class EndRunActivity: BindingActivity(R.layout.activity_e companion object { const val MAX_TITLE_LENGTH = 20 const val MESSAGE_MAX_TITLE_LENGTH = "최대 20자까지 입력 가능합니다" - const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" const val EXTRA_RUN_TO_ENDRUN = "RunToEndRunData" } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapper.kt b/app/src/main/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapper.kt new file mode 100644 index 00000000..8fecb7a0 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapper.kt @@ -0,0 +1,14 @@ +package com.runnect.runnect.presentation.navigation + +/** + * EndRunActivity가 읽는 CourseData.dataFrom(String) 값을 MainTab으로 변환한다. + * CourseData.dataFrom의 타입을 바꾸는 건 DrawActivity/CourseDetailActivity/MyDrawDetailActivity까지 + * 연쇄적으로 건드리게 되어 스코프 밖이라, 여기서만 국소적으로 매핑한다. + * MainActivity.checkIntentValue()의 기존 when 분기를 그대로 옮긴 것이라 동작 변화는 없다 — + * "detail"처럼 기존에도 매칭되지 않던 값은 그대로 매칭 없음(null, 기본 그리기 탭)으로 남는다. + */ +fun mapDataFromToMainTab(dataFrom: String?): MainTab? = when (dataFrom) { + "fromDrawCourse", "fromDeleteMyDrawDetail", "fromMyDrawDetail" -> MainTab.STORAGE + "fromMyScrap", "fromCourseDetail" -> MainTab.DISCOVER + else -> null +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/navigation/Navigator.kt b/app/src/main/java/com/runnect/runnect/presentation/navigation/Navigator.kt new file mode 100644 index 00000000..718857c3 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/navigation/Navigator.kt @@ -0,0 +1,38 @@ +package com.runnect.runnect.presentation.navigation + +import android.content.Context + +/** MainActivity가 랜딩 탭을 수신할 때 쓰는 Intent extra 키. Navigator 구현체와 MainActivity만 참조한다. */ +const val EXTRA_MAIN_TAB = "extraMainTab" + +interface Navigator { + /** + * MainActivity로 이동(또는 재사용)하며 랜딩할 탭을 지정한다. + * @param tab null이면 탭을 지정하지 않는다(기본 그리기 탭 유지). + */ + fun navigateToMain( + context: Context, + tab: MainTab? = null, + mode: NavigationMode = NavigationMode.DEFAULT + ) +} + +enum class MainTab { + DRAWING, STORAGE, DISCOVER, MY_PAGE +} + +/** + * MainActivity 재진입 시 태스크/백스택 처리 방식. + * 2단계(NavController 전환)에서는 popUpTo/inclusive 등으로 재해석될 수 있는 "의미"만 + * 표현하고, 실제 Intent 플래그 매핑은 구현체가 담당한다. + */ +enum class NavigationMode { + /** 플래그 없음. */ + DEFAULT, + + /** FLAG_ACTIVITY_CLEAR_TOP */ + CLEAR_TOP, + + /** FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK */ + NEW_TASK_CLEAR_TASK +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/navigation/NavigatorImpl.kt b/app/src/main/java/com/runnect/runnect/presentation/navigation/NavigatorImpl.kt new file mode 100644 index 00000000..ee4627c9 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/navigation/NavigatorImpl.kt @@ -0,0 +1,24 @@ +package com.runnect.runnect.presentation.navigation + +import android.content.Context +import android.content.Intent +import com.runnect.runnect.presentation.MainActivity +import javax.inject.Inject + +class NavigatorImpl @Inject constructor() : Navigator { + + override fun navigateToMain(context: Context, tab: MainTab?, mode: NavigationMode) { + val intent = Intent(context, MainActivity::class.java).apply { + tab?.let { putExtra(EXTRA_MAIN_TAB, it) } + addFlags(mode.toIntentFlags()) + } + context.startActivity(intent) + } + + private fun NavigationMode.toIntentFlags(): Int = when (this) { + NavigationMode.DEFAULT -> 0 + NavigationMode.CLEAR_TOP -> Intent.FLAG_ACTIVITY_CLEAR_TOP + NavigationMode.NEW_TASK_CLEAR_TASK -> + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } +} diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt index 5d9446bd..46441c99 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt @@ -18,11 +18,12 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import com.runnect.runnect.R import com.runnect.runnect.domain.entity.MyScrapCourse -import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.detail.CourseDetailActivity import com.runnect.runnect.presentation.detail.CourseDetailRootScreen import com.runnect.runnect.presentation.event.ScreenRefreshEvent import com.runnect.runnect.presentation.event.ScreenRefreshEventBus +import com.runnect.runnect.presentation.navigation.MainTab +import com.runnect.runnect.presentation.navigation.Navigator import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.presentation.ui.theme.RunnectTheme import com.runnect.runnect.util.analytics.Analytics @@ -37,6 +38,9 @@ class StorageScrapFragment : Fragment() { @Inject lateinit var screenRefreshEventBus: ScreenRefreshEventBus + @Inject + lateinit var navigator: Navigator + private val viewModel: StorageViewModel by viewModels() override fun onCreateView( @@ -132,10 +136,7 @@ class StorageScrapFragment : Fragment() { } private fun navigateToDiscover() { - val intent = Intent(activity, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, "fromMyScrap") - } - startActivity(intent) + navigator.navigateToMain(requireContext(), MainTab.DISCOVER) requireActivity().overridePendingTransition( R.anim.slide_in_right, R.anim.slide_out_left @@ -143,7 +144,6 @@ class StorageScrapFragment : Fragment() { } companion object { - const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" const val EXTRA_PUBLIC_COURSE_ID = "publicCourseId" const val EXTRA_ROOT_SCREEN = "rootScreen" } diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt index c1aad698..70b506e4 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt @@ -21,6 +21,9 @@ import com.runnect.runnect.databinding.LayoutCommonToolbarBinding import com.runnect.runnect.domain.entity.MyDrawCourseDetail import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.countdown.CountDownActivity +import com.runnect.runnect.presentation.navigation.MainTab +import com.runnect.runnect.presentation.navigation.NavigationMode +import com.runnect.runnect.presentation.navigation.Navigator import com.runnect.runnect.presentation.scheme.SchemeActivity import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.presentation.storage.StorageMyDrawFragment @@ -41,11 +44,15 @@ import com.runnect.runnect.util.extension.showSnackbar import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.extension.showWebBrowser import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MyDrawDetailActivity : BindingActivity(R.layout.activity_my_draw_detail), CommonToolbarLayout { + @Inject + lateinit var navigator: Navigator + val viewModel: MyDrawDetailViewModel by viewModels() private lateinit var myDrawCourseDetail: MyDrawCourseDetail private lateinit var departureLatLng: LatLng @@ -203,7 +210,7 @@ class MyDrawDetailActivity : departure = course.departureName, distance = course.distance, image = course.imgUrl, - dataFrom = EXTRA_FROM_MY_DRAW_DETAIL + dataFrom = DATA_FROM_MY_DRAW_DETAIL ) } @@ -211,11 +218,7 @@ class MyDrawDetailActivity : viewModel.courseDeleteState.observe(this) { state -> when (state) { is UiStateV2.Success -> { - Intent(this@MyDrawDetailActivity, MainActivity::class.java).apply { - putExtra(EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, EXTRA_DELETE_MY_DRAW_COURSE) - }.apply { - startActivity(this) - } + navigator.navigateToMain(this@MyDrawDetailActivity, MainTab.STORAGE) navigateToPreviousScreenWithAnimation() } @@ -389,14 +392,7 @@ class MyDrawDetailActivity : } private fun navigateToMainScreen() { - Intent(this, MainActivity::class.java).apply { - putExtra( - EXTRA_FRAGMENT_REPLACEMENT_DIRECTION, - EXTRA_FROM_MY_DRAW_DETAIL - ) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - startActivity(this) - } + navigator.navigateToMain(this, MainTab.STORAGE, NavigationMode.NEW_TASK_CLEAR_TASK) applyScreenExitAnimation() } @@ -411,9 +407,9 @@ class MyDrawDetailActivity : private const val REPORT_URL = "https://docs.google.com/forms/d/e/1FAIpQLSek2rkClKfGaz1zwTEHX3Oojbq_pbF3ifPYMYezBU0_pe-_Tg/viewform" private const val TAG_MY_DRAW_COURSE_DELETE_DIALOG = "MY_DRAW_COURSE_DELETE_DIALOG" - private const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection" - private const val EXTRA_FROM_MY_DRAW_DETAIL = "fromMyDrawDetail" - private const val EXTRA_DELETE_MY_DRAW_COURSE = "fromDeleteMyDrawDetail" + + /** Intent extra 키가 아니라 CourseData.dataFrom에 실리는 데이터 마커 문자열. */ + private const val DATA_FROM_MY_DRAW_DETAIL = "fromMyDrawDetail" private const val EXTRA_COURSE_DATA = "CourseData" } } \ No newline at end of file diff --git a/app/src/test/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapperTest.kt b/app/src/test/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapperTest.kt new file mode 100644 index 00000000..9a85ab9b --- /dev/null +++ b/app/src/test/java/com/runnect/runnect/presentation/navigation/MainTabDataFromMapperTest.kt @@ -0,0 +1,31 @@ +package com.runnect.runnect.presentation.navigation + +import org.junit.Assert.assertEquals +import org.junit.Test + +class MainTabDataFromMapperTest { + + @Test + fun `그리기 관련 dataFrom 값은 STORAGE로 매핑된다`() { + assertEquals(MainTab.STORAGE, mapDataFromToMainTab("fromDrawCourse")) + assertEquals(MainTab.STORAGE, mapDataFromToMainTab("fromDeleteMyDrawDetail")) + assertEquals(MainTab.STORAGE, mapDataFromToMainTab("fromMyDrawDetail")) + } + + @Test + fun `코스발견 관련 dataFrom 값은 DISCOVER로 매핑된다`() { + assertEquals(MainTab.DISCOVER, mapDataFromToMainTab("fromMyScrap")) + assertEquals(MainTab.DISCOVER, mapDataFromToMainTab("fromCourseDetail")) + } + + @Test + fun `매칭되지 않는 값은 null로 매핑된다`() { + assertEquals(null, mapDataFromToMainTab("detail")) + assertEquals(null, mapDataFromToMainTab("unknown")) + } + + @Test + fun `null 입력은 null로 매핑된다`() { + assertEquals(null, mapDataFromToMainTab(null)) + } +} diff --git a/app/src/test/java/com/runnect/runnect/presentation/navigation/NavigatorImplTest.kt b/app/src/test/java/com/runnect/runnect/presentation/navigation/NavigatorImplTest.kt new file mode 100644 index 00000000..2ee858ab --- /dev/null +++ b/app/src/test/java/com/runnect/runnect/presentation/navigation/NavigatorImplTest.kt @@ -0,0 +1,73 @@ +package com.runnect.runnect.presentation.navigation + +import android.content.Context +import android.content.Intent +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.unmockkConstructor +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test + +class NavigatorImplTest { + + private lateinit var navigator: NavigatorImpl + private lateinit var context: Context + + @Before + fun setUp() { + navigator = NavigatorImpl() + context = mockk(relaxed = true) + mockkConstructor(Intent::class) + every { anyConstructed().putExtra(any(), any()) } returns mockk(relaxed = true) + every { anyConstructed().addFlags(any()) } returns mockk(relaxed = true) + } + + @After + fun tearDown() { + unmockkConstructor(Intent::class) + } + + @Test + fun `DEFAULT 모드는 플래그를 추가하지 않는다`() { + navigator.navigateToMain(context, MainTab.STORAGE, NavigationMode.DEFAULT) + + verify { anyConstructed().addFlags(0) } + verify { context.startActivity(any()) } + } + + @Test + fun `CLEAR_TOP 모드는 FLAG_ACTIVITY_CLEAR_TOP을 추가한다`() { + navigator.navigateToMain(context, MainTab.DISCOVER, NavigationMode.CLEAR_TOP) + + verify { anyConstructed().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } + } + + @Test + fun `NEW_TASK_CLEAR_TASK 모드는 두 플래그를 함께 추가한다`() { + navigator.navigateToMain(context, MainTab.STORAGE, NavigationMode.NEW_TASK_CLEAR_TASK) + + verify { + anyConstructed().addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + ) + } + } + + @Test + fun `tab이 지정되면 EXTRA_MAIN_TAB extra를 담는다`() { + navigator.navigateToMain(context, MainTab.MY_PAGE, NavigationMode.DEFAULT) + + verify { anyConstructed().putExtra(EXTRA_MAIN_TAB, MainTab.MY_PAGE) } + } + + @Test + fun `tab이 null이면 extra를 담지 않는다`() { + navigator.navigateToMain(context, null, NavigationMode.DEFAULT) + + verify(exactly = 0) { anyConstructed().putExtra(any(), any()) } + verify { context.startActivity(any()) } + } +}