diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9a85b9266..9a8d3586c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,6 @@ android:host="oauth" android:scheme="kakao${NATIVE_APP_KEY}" /> - diff --git a/core/designsystem/src/main/java/com/terning/core/designsystem/type/NotificationRedirect.kt b/core/designsystem/src/main/java/com/terning/core/designsystem/type/NotificationRedirect.kt index 9b85a3d8b..221aa43c0 100644 --- a/core/designsystem/src/main/java/com/terning/core/designsystem/type/NotificationRedirect.kt +++ b/core/designsystem/src/main/java/com/terning/core/designsystem/type/NotificationRedirect.kt @@ -1,12 +1,14 @@ package com.terning.core.designsystem.type -enum class NotificationRedirect(val path: String) { +enum class DeeplinkType(val path: String) { CALENDAR("calendar"), HOME("home"), - SEARCH("search"); + SEARCH("search"), + KAKAOLINK("kakaolink"), + INTERN("intern"); companion object { - fun from(type: String?): NotificationRedirect? = + fun from(type: String?): DeeplinkType? = entries.firstOrNull { it.path.equals(type, ignoreCase = true) } } } diff --git a/core/designsystem/src/main/java/com/terning/core/designsystem/util/DeeplinkDefaults.kt b/core/designsystem/src/main/java/com/terning/core/designsystem/util/DeeplinkDefaults.kt index 211113ff9..fd105e119 100644 --- a/core/designsystem/src/main/java/com/terning/core/designsystem/util/DeeplinkDefaults.kt +++ b/core/designsystem/src/main/java/com/terning/core/designsystem/util/DeeplinkDefaults.kt @@ -1,7 +1,24 @@ package com.terning.core.designsystem.util +import android.net.Uri + object DeeplinkDefaults { + private const val SCHEME: String = "terning" const val REDIRECT: String = "redirect" + const val INTERN_ID: String = "internId" + + fun build( + host: String, + redirect: String? = null, + internId: String? = null + ): String { + val uriBuilder = Uri.Builder() + .scheme(SCHEME) + .authority(host) + + redirect?.let { uriBuilder.appendQueryParameter(REDIRECT, it) } + internId?.let { uriBuilder.appendQueryParameter(INTERN_ID, it) } - fun build(base: String) = "terning://${base}" -} \ No newline at end of file + return uriBuilder.build().toString() + } +} diff --git a/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt b/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt index 267575616..cabf68afb 100644 --- a/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt +++ b/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt @@ -37,7 +37,6 @@ class KakaoUtil @Inject constructor( } catch (e: Exception) { Timber.e("웹 공유 실패: ${e.message}") } - } } } diff --git a/core/firebase/src/main/java/com/terning/core/firebase/messageservice/TerningMessagingService.kt b/core/firebase/src/main/java/com/terning/core/firebase/messageservice/TerningMessagingService.kt index 5996f7fc7..8c9a7493c 100644 --- a/core/firebase/src/main/java/com/terning/core/firebase/messageservice/TerningMessagingService.kt +++ b/core/firebase/src/main/java/com/terning/core/firebase/messageservice/TerningMessagingService.kt @@ -16,9 +16,8 @@ import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.terning.core.analytics.AmplitudeTracker import com.terning.core.analytics.EventType -import com.terning.core.designsystem.type.NotificationRedirect +import com.terning.core.designsystem.type.DeeplinkType import com.terning.core.designsystem.util.DeeplinkDefaults -import com.terning.core.designsystem.util.DeeplinkDefaults.REDIRECT import com.terning.core.firebase.R import com.terning.domain.user.repository.UserRepository import com.terning.navigator.NavigatorProvider @@ -162,10 +161,10 @@ class TerningMessagingService : FirebaseMessagingService() { } private fun buildDeeplink(type: String, isForeground: Boolean): String { - val base = NotificationRedirect.from(type) ?: return "" + val base = DeeplinkType.from(type) ?: return "" - return if (isForeground) DeeplinkDefaults.build(base.path) - else DeeplinkDefaults.build("splash?$REDIRECT=${base.path}") + return if (isForeground) DeeplinkDefaults.build(host = base.path) + else DeeplinkDefaults.build(host = BACKGROUND_HOST, redirect = base.path) } companion object { @@ -174,6 +173,7 @@ class TerningMessagingService : FirebaseMessagingService() { private const val BODY: String = "body" private const val TYPE: String = "type" private const val IMAGE_URL: String = "imageUrl" + private const val BACKGROUND_HOST: String = "splash" const val FROM_NOTIFICATION: String = "fromNotification" } } diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt index e1a3c0fb5..0e9afed13 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment @@ -81,6 +82,7 @@ private const val ZERO_TOTAL_COUNT = 0 @OptIn(ExperimentalPermissionsApi::class) @Composable fun HomeRoute( + internId: String?, paddingValues: PaddingValues, viewModel: HomeViewModel = hiltViewModel(), navigateToCalendar: () -> Unit, @@ -103,6 +105,15 @@ fun HomeRoute( } } + var hasHandledInternDeeplink by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(internId, hasHandledInternDeeplink) { + if (internId != null && !hasHandledInternDeeplink) { + navigateToIntern(internId.toLong()) + hasHandledInternDeeplink = true + } + } + LaunchedEffect(key1 = true) { viewModel.getProfile() viewModel.getFilteringInfo() diff --git a/feature/home/src/main/java/com/terning/feature/home/navigation/HometNavigation.kt b/feature/home/src/main/java/com/terning/feature/home/navigation/HomeNavigation.kt similarity index 75% rename from feature/home/src/main/java/com/terning/feature/home/navigation/HometNavigation.kt rename to feature/home/src/main/java/com/terning/feature/home/navigation/HomeNavigation.kt index 3108a889e..fb47438c0 100644 --- a/feature/home/src/main/java/com/terning/feature/home/navigation/HometNavigation.kt +++ b/feature/home/src/main/java/com/terning/feature/home/navigation/HomeNavigation.kt @@ -8,14 +8,19 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.navDeepLink +import androidx.navigation.toRoute import com.terning.core.designsystem.util.DeeplinkDefaults import com.terning.core.navigation.MainTabRoute import com.terning.feature.home.HomeRoute +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -fun NavController.navigateHome(navOptions: NavOptions? = null) { +fun NavController.navigateHome( + internId: String?, + navOptions: NavOptions? = null +) { navigate( - route = Home, + route = Home(internId), navOptions = navOptions ) } @@ -33,7 +38,9 @@ fun NavGraphBuilder.homeNavGraph( ) ) ) { + val args = it.toRoute() HomeRoute( + internId = args.internId, paddingValues = paddingValues, navigateToCalendar = navigateToCalendar, navigateToIntern = navigateToIntern @@ -42,4 +49,7 @@ fun NavGraphBuilder.homeNavGraph( } @Serializable -data object Home : MainTabRoute \ No newline at end of file +data class Home( + @SerialName("internId") + val internId: String? = null +) : MainTabRoute diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt index 7f52a79cf..af2bd8564 100644 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt @@ -85,7 +85,8 @@ fun InternRoute( navController = navController, onClickShareButton = { viewModel.onKakaoShareClicked( - (internState.loadState as UiState.Success).data + (internState.loadState as UiState.Success).data, + announcementId.toString() ) }, onDismissCancelDialog = { diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt index 05dbd5df8..cfd954b3e 100644 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt @@ -19,7 +19,7 @@ import javax.inject.Inject @HiltViewModel class InternViewModel @Inject constructor( private val internRepository: InternRepository, - private val kakaoUtil: KakaoUtil + private val kakaoUtil: KakaoUtil, ) : ViewModel() { private val _internUiState = MutableStateFlow(InternUiState()) @@ -68,7 +68,8 @@ class InternViewModel @Inject constructor( } fun onKakaoShareClicked( - internInfo: InternInfo + internInfo: InternInfo, + announcementId: String, ) { val templateArgs = mapOf( "COMPANY_IMG" to internInfo.companyImage, @@ -76,8 +77,15 @@ class InternViewModel @Inject constructor( "DEADLINE" to internInfo.deadline, "START_DATE" to internInfo.startYearMonth, "PERIOD" to internInfo.workingPeriod, - "REGI_WEB_DOMAIN" to internInfo.url - ) + "A_E" to "kakaolink", + "A_E_D" to "intern", + "I_E" to "kakaolink", + "I_E_D" to "jobDetail", + "redirect" to "intern", + "internId" to announcementId, + "action" to "jobDetail", + "JOB_ID" to announcementId + ) kakaoUtil.shareToKakaoTalk(templateArgs) } } diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt deleted file mode 100644 index 9954265af..000000000 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt +++ /dev/null @@ -1,7 +0,0 @@ - -import androidx.annotation.StringRes - -sealed class InternViewSideEffect { - data class Toast(@StringRes val message: Int) : - InternViewSideEffect() -} \ No newline at end of file diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts index 79698c6ef..cd074e5cb 100644 --- a/feature/main/build.gradle.kts +++ b/feature/main/build.gradle.kts @@ -1,11 +1,28 @@ import com.terning.build_logic.extension.setNamespace +import java.util.Properties plugins { alias(libs.plugins.terning.feature) } +val properties = Properties().apply { + load(rootProject.file("local.properties").inputStream()) +} + android { + buildFeatures { + buildConfig = true + } + setNamespace("feature.main") + + defaultConfig { + buildConfigField( + "String", + "NATIVE_APP_KEY", + properties.getProperty("native.app.key"), + ) + } } dependencies { diff --git a/feature/main/src/main/AndroidManifest.xml b/feature/main/src/main/AndroidManifest.xml index acad7f973..7088005cb 100644 --- a/feature/main/src/main/AndroidManifest.xml +++ b/feature/main/src/main/AndroidManifest.xml @@ -24,6 +24,18 @@ + + + + + + + + + + { + private fun handleDeeplink(intent: Intent?): Triple { val uri = intent?.data val uriString = uri?.toString() - if (uriString.isNullOrEmpty()) return null to null + if (uriString.isNullOrEmpty()) return Triple(null, null, null) val host = uri.host val redirect = uri.getQueryParameter(REDIRECT) + val internId = uri.getQueryParameter(INTERN_ID) if (!intent.getBooleanExtra(ALREADY_TRACKED, false) && intent.getBooleanExtra(FROM_NOTIFICATION, false) @@ -64,7 +68,7 @@ class MainActivity : ComponentActivity() { intent.putExtra(ALREADY_TRACKED, true) - return host to redirect + return Triple(host, redirect, internId) } companion object { @@ -74,4 +78,4 @@ class MainActivity : ComponentActivity() { context: Context, ) = Intent(context, MainActivity::class.java) } -} \ No newline at end of file +} diff --git a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt index 9fd4f965f..ded0c4e9e 100644 --- a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt @@ -8,7 +8,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions -import com.terning.core.designsystem.type.NotificationRedirect +import com.terning.core.designsystem.type.DeeplinkType import com.terning.feature.calendar.calendar.navigation.Calendar import com.terning.feature.calendar.calendar.navigation.navigateCalendar import com.terning.feature.home.navigation.Home @@ -26,13 +26,18 @@ class MainNavigator( @Composable get() = navController .currentBackStackEntryAsState().value?.destination - fun getStartDestination(redirect: String?, host: String?) = - when (NotificationRedirect.from(host)) { - NotificationRedirect.SEARCH -> Search - NotificationRedirect.HOME -> Home - NotificationRedirect.CALENDAR -> Calendar - else -> Splash(redirect) - } + fun getStartDestination( + host: String?, + redirect: String?, + internId: String? + ) = when (DeeplinkType.from(host)) { + DeeplinkType.SEARCH -> Search + DeeplinkType.HOME -> Home(internId = null) + DeeplinkType.CALENDAR -> Calendar + DeeplinkType.KAKAOLINK -> Splash(redirect = redirect, internId = internId) + + else -> Splash(redirect = redirect) + } val currentTab: MainTab? @Composable get() = MainTab.find { tab -> @@ -52,7 +57,7 @@ class MainNavigator( } when (tab) { - MainTab.HOME -> navController.navigateHome(navOptions) + MainTab.HOME -> navController.navigateHome(navOptions = navOptions, internId = null) MainTab.CALENDAR -> navController.navigateCalendar(navOptions) MainTab.SEARCH -> navController.navigateSearch(navOptions) MainTab.MY_PAGE -> navController.navigateMyPage(navOptions) diff --git a/feature/main/src/main/java/com/terning/feature/main/MainScreen.kt b/feature/main/src/main/java/com/terning/feature/main/MainScreen.kt index 2dbbb7f53..45074d91f 100644 --- a/feature/main/src/main/java/com/terning/feature/main/MainScreen.kt +++ b/feature/main/src/main/java/com/terning/feature/main/MainScreen.kt @@ -65,6 +65,7 @@ import kotlinx.coroutines.launch fun MainScreen( host: String?, redirect: String?, + internId: String?, navigator: MainNavigator = rememberMainNavigator(), ) { val context = LocalContext.current @@ -90,7 +91,7 @@ fun MainScreen( val amplitudeTracker = LocalTracker.current val splashNavOptions = NavOptions.Builder().setPopUpTo( - route = Splash(redirect), + route = Splash(redirect = redirect, internId = internId), inclusive = true ).build() @@ -145,11 +146,18 @@ fun MainScreen( ExitTransition.None }, navController = navigator.navController, - startDestination = navigator.getStartDestination(redirect = redirect, host = host) + startDestination = navigator.getStartDestination( + host = host, + redirect = redirect, + internId = internId + ) ) { splashNavGraph( - navigateHome = { - navigator.navController.navigateHome(navOptions = splashNavOptions) + navigateHome = { internId -> + navigator.navController.navigateHome( + navOptions = splashNavOptions, + internId = internId + ) }, navigateSignIn = { navigator.navController.navigateSignIn(navOptions = splashNavOptions) @@ -188,7 +196,10 @@ fun MainScreen( inclusive = true } } - navigator.navController.navigateHome(navOptions) + navigator.navController.navigateHome( + navOptions = navOptions, + internId = null + ) }, navigateSignUp = { authId -> val navOptions = navOptions { @@ -225,7 +236,10 @@ fun MainScreen( inclusive = true } } - navigator.navController.navigateHome(navOptions) + navigator.navController.navigateHome( + navOptions = navOptions, + internId = null + ) } ) startHomeNavGraph( @@ -235,7 +249,10 @@ fun MainScreen( inclusive = true } } - navigator.navController.navigateHome(navOptions) + navigator.navController.navigateHome( + navOptions = navOptions, + internId = null + ) } ) filteringOneNavGraph(navHostController = navigator.navController) diff --git a/feature/main/src/main/java/com/terning/feature/main/MainTab.kt b/feature/main/src/main/java/com/terning/feature/main/MainTab.kt index 5d0868749..44048defd 100644 --- a/feature/main/src/main/java/com/terning/feature/main/MainTab.kt +++ b/feature/main/src/main/java/com/terning/feature/main/MainTab.kt @@ -18,7 +18,7 @@ enum class MainTab( HOME( icon = R.drawable.ic_nav_home, contentDescription = R.string.bottom_nav_home, - route = Home + route = Home(null) ), CALENDAR( icon = R.drawable.ic_nav_calendar, diff --git a/feature/splash/src/main/java/com/terning/feature/splash/SplashRoute.kt b/feature/splash/src/main/java/com/terning/feature/splash/SplashRoute.kt index 49a5fbbc0..ba4458e4f 100644 --- a/feature/splash/src/main/java/com/terning/feature/splash/SplashRoute.kt +++ b/feature/splash/src/main/java/com/terning/feature/splash/SplashRoute.kt @@ -28,7 +28,7 @@ import com.terning.core.designsystem.extension.launchPlayStore import com.terning.core.designsystem.theme.TerningMain import com.terning.core.designsystem.theme.TerningPointTheme import com.terning.core.designsystem.theme.White -import com.terning.core.designsystem.type.NotificationRedirect +import com.terning.core.designsystem.type.DeeplinkType import com.terning.domain.update.entity.UpdateState import com.terning.feature.splash.component.TerningMajorUpdateDialog import com.terning.feature.splash.component.TerningPatchUpdateDialog @@ -37,7 +37,8 @@ import kotlinx.coroutines.launch @Composable internal fun SplashRoute( redirect: String?, - navigateToHome: () -> Unit, + internId: String?, + navigateToHome: (internId: String?) -> Unit, navigateToSignIn: () -> Unit, navigateToCalendar: () -> Unit, navigateToSearch: () -> Unit, @@ -73,6 +74,7 @@ internal fun SplashRoute( } } } + lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) @@ -85,15 +87,13 @@ internal fun SplashRoute( when (sideEffect) { is SplashSideEffect.HasAccessToken -> { if (sideEffect.hasAccessToken) { - if (redirect.isNullOrBlank()) { - navigateToHome() - } else { - when (NotificationRedirect.from(redirect)) { - NotificationRedirect.CALENDAR -> navigateToCalendar() - NotificationRedirect.HOME -> navigateToHome() - NotificationRedirect.SEARCH -> navigateToSearch() - else -> navigateToHome() - } + when (DeeplinkType.from(redirect)) { + DeeplinkType.CALENDAR -> navigateToCalendar() + DeeplinkType.HOME -> navigateToHome(null) + DeeplinkType.SEARCH -> navigateToSearch() + DeeplinkType.INTERN -> navigateToHome(internId.orEmpty()) + + else -> navigateToHome(null) } } else { navigateToSignIn() diff --git a/feature/splash/src/main/java/com/terning/feature/splash/navigation/SplashNavigation.kt b/feature/splash/src/main/java/com/terning/feature/splash/navigation/SplashNavigation.kt index 301103528..1f683533b 100644 --- a/feature/splash/src/main/java/com/terning/feature/splash/navigation/SplashNavigation.kt +++ b/feature/splash/src/main/java/com/terning/feature/splash/navigation/SplashNavigation.kt @@ -11,10 +11,10 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable fun NavGraphBuilder.splashNavGraph( - navigateHome: () -> Unit, + navigateHome: (internId: String?) -> Unit, navigateSignIn: () -> Unit, navigateSearch: () -> Unit, - navigateCalendar: () -> Unit + navigateCalendar: () -> Unit, ) { composable( deepLinks = listOf( @@ -26,6 +26,7 @@ fun NavGraphBuilder.splashNavGraph( val args = it.toRoute() SplashRoute( redirect = args.redirect, + internId = args.internId, navigateToHome = navigateHome, navigateToSignIn = navigateSignIn, navigateToSearch = navigateSearch, @@ -37,5 +38,7 @@ fun NavGraphBuilder.splashNavGraph( @Serializable data class Splash( @SerialName("redirect") - val redirect: String? -) : Route \ No newline at end of file + val redirect: String? = null, + @SerialName("internId") + val internId: String? = null +) : Route