diff --git a/app/build.gradle b/app/build.gradle
index d57d8805..1c86a120 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -78,6 +78,7 @@ dependencies {
implementation 'androidx.core:core:1.9.0'
implementation 'com.google.firebase:firebase-common-ktx:20.1.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
+ implementation 'com.google.firebase:firebase-dynamic-links-ktx:21.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 337e58fb..68a843e6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -118,10 +118,9 @@
-
+ android:host="rnnt.page.link"
+ android:scheme="https" />
diff --git a/app/src/main/java/com/runnect/runnect/data/service/TokenAuthenticator.kt b/app/src/main/java/com/runnect/runnect/data/service/TokenAuthenticator.kt
index ee2b0c43..843851b6 100644
--- a/app/src/main/java/com/runnect/runnect/data/service/TokenAuthenticator.kt
+++ b/app/src/main/java/com/runnect/runnect/data/service/TokenAuthenticator.kt
@@ -6,7 +6,6 @@ import android.os.Handler
import android.os.Looper
import android.widget.Toast
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
-import com.runnect.runnect.BuildConfig
import com.runnect.runnect.R
import com.runnect.runnect.application.ApplicationClass
import com.runnect.runnect.application.PreferenceManager
@@ -41,17 +40,6 @@ class TokenAuthenticator(val context: Context) : Authenticator {
return null
}
- private fun clearToken() {
- PreferenceManager.setString(
- context,
- TOKEN_KEY_ACCESS, "none"
- )
- PreferenceManager.setString(
- context,
- TOKEN_KEY_REFRESH, "none"
- )
- }
-
private suspend inline fun getNewDeviceToken(): Boolean {
return withContext(Dispatchers.IO) {
//토큰 재발급
@@ -69,10 +57,9 @@ class TokenAuthenticator(val context: Context) : Authenticator {
return true
}.onFailure {
Timber.tag("test").d("callRefresh-onFailure")
- //이 부분을 onSuccess 안에 추가해야 하는 것인지, onFailure에 두는 것이 맞는지 헷갈립니다.
if (it.message == "Unauthorized") {
Timber.tag("test").d("callRefresh-onFailure-inner-if")
- clearToken()
+ PreferenceManager.clear(context)
Handler(Looper.getMainLooper()).post {
Toast.makeText(
context,
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 3e889dd3..1233bd97 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
@@ -1,27 +1,20 @@
package com.runnect.runnect.presentation.detail
import android.app.Activity
-import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Rect
+import android.net.Uri
import android.os.Bundle
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.EditText
-import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.core.view.isVisible
import coil.load
-import com.gun0912.tedpermission.provider.TedPermissionProvider.context
-import com.kakao.sdk.common.util.KakaoCustomTabsClient
-import com.kakao.sdk.link.LinkClient
-import com.kakao.sdk.link.WebSharerClient
-import com.kakao.sdk.template.model.Button
-import com.kakao.sdk.template.model.Content
-import com.kakao.sdk.template.model.FeedTemplate
-import com.kakao.sdk.template.model.Link
+import com.google.firebase.dynamiclinks.DynamicLink
+import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
import com.naver.maps.geometry.LatLng
import com.runnect.runnect.R
import com.runnect.runnect.binding.BindingActivity
@@ -90,33 +83,44 @@ class CourseDetailActivity :
Analytics.logClickedItemEvent(VIEW_COURSE_DETAIL)
- initIntentExtraData()
- updatePublicCourseIdFromDeepLink()
- getCourseDetail()
-
- addListener()
- addObserver()
- registerBackPressedCallback()
+ updatePublicCourseIdFromDynamicLink { dynamicLinkHandled ->
+ if (!dynamicLinkHandled) {
+ initIntentExtraData()
+ }
+ addListener()
+ addObserver()
+ registerBackPressedCallback()
+ getCourseDetail()
+ }
}
private fun initIntentExtraData() {
intent.getCompatibleSerializableExtra(EXTRA_ROOT_SCREEN)?.let {
rootScreen = it
}
- publicCourseId = intent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, 0)
- }
-
- private fun updatePublicCourseIdFromDeepLink() {
- // 딥링크를 통해 열린 경우
- if (Intent.ACTION_VIEW == intent.action) {
- isFromDeepLink = true
- val uri = intent.data
- if (uri != null) {
- // 여기서 androidExecutionParams 값들을 받아와 어떠한 상세 페이지를 띄울지 결정할 수 있음.
- publicCourseId = uri.getQueryParameter("publicCourseId")!!.toInt()
- Timber.tag("deeplink-publicCourseId").d("$publicCourseId")
+ publicCourseId = intent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, -1)
+ Timber.tag("intent-publicCourseId").d("$publicCourseId")
+ }
+
+ private fun updatePublicCourseIdFromDynamicLink(completion: (Boolean) -> Unit) {
+ FirebaseDynamicLinks.getInstance().getDynamicLink(intent)
+ .addOnSuccessListener(this) { pendingDynamicLinkData ->
+ val deepLink: Uri? = pendingDynamicLinkData?.link
+ if (deepLink != null) {
+ isFromDeepLink = true
+ publicCourseId = deepLink.getQueryParameter("courseId")?.toInt() ?: -1
+ if (publicCourseId != -1) {
+ Timber.tag("deeplink-publicCourseId").d("$publicCourseId")
+ completion(true)
+ return@addOnSuccessListener
+ }
+ }
+ completion(false)
+ }
+ .addOnFailureListener(this) { e ->
+ Timber.e("getDynamicLink:onFailure", e)
+ completion(false)
}
- }
}
private fun getCourseDetail() {
@@ -163,7 +167,7 @@ class CourseDetailActivity :
intent?.let { newIntent ->
newIntent.getCompatibleSerializableExtra(EXTRA_ROOT_SCREEN)
?.let { rootScreen = it }
- publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, 0)
+ publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, -1)
getCourseDetail()
}
}
@@ -238,9 +242,42 @@ class CourseDetailActivity :
}
}
+ private fun sendFirebaseDynamicLink(title: String, desc: String, image: String) {
+ val link = "https://rnnt.page.link/?courseId=$publicCourseId"
+
+ FirebaseDynamicLinks.getInstance().createDynamicLink()
+ .setLink(Uri.parse(link))
+ .setDomainUriPrefix("https://rnnt.page.link")
+ .setAndroidParameters(DynamicLink.AndroidParameters.Builder().build())
+ .setIosParameters(DynamicLink.IosParameters.Builder("com.runnect.Runnect-iOS").build())
+ .setSocialMetaTagParameters(
+ DynamicLink.SocialMetaTagParameters.Builder()
+ .setTitle(title)
+ .setDescription(desc)
+ .setImageUrl(Uri.parse(image))
+ .build()
+ )
+ .buildShortDynamicLink()
+ .addOnSuccessListener { result ->
+ val shortLink = result.shortLink
+ shareLink(shortLink.toString())
+ }
+ .addOnFailureListener {
+ it.printStackTrace()
+ }
+ }
+
+ private fun shareLink(url: String) {
+ val intent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, url)
+ }
+ startActivity(Intent.createChooser(intent, "Share Link"))
+ }
+
private fun initShareButtonClickListener() {
binding.btnShare.setOnClickListener {
- sendKakaoLink(
+ sendFirebaseDynamicLink(
title = courseDetail.title,
desc = courseDetail.description,
image = courseDetail.image
@@ -255,71 +292,6 @@ class CourseDetailActivity :
}
}
- // todo: 함수를 더 작게 분리하는 게 좋을 거 같아요! @우남
- private fun sendKakaoLink(title: String, desc: String, image: String) {
- // 메시지 템플릿 만들기 (피드형)
- val defaultFeed = FeedTemplate(
- content = Content(
- title = title,
- description = desc,
- imageUrl = image,
- link = Link(
- mobileWebUrl = "https://play.google.com/store/apps/details?id=com.runnect.runnect"
- )
- ),
- buttons = listOf(
- Button(
- "자세히 보기",
- Link(
- //이 부분을 사용해서 어떤 상세페이지를 띄울지 결정할수 있다
- androidExecutionParams = mapOf(
- "publicCourseId" to publicCourseId.toString(),
- )
- ),
- )
- )
- )
-
- // 피드 메시지 보내기
- if (context?.let { LinkClient.instance.isKakaoLinkAvailable(it) } == true) {
- // 카카오톡으로 카카오링크 공유 가능
- context?.let {
- LinkClient.instance.defaultTemplate(it, defaultFeed) { linkResult, error ->
- if (error != null) {
- Timber.tag("kakao_link").d("카카오링크 보내기 실패: $error")
- } else if (linkResult != null) {
- Timber.tag("kakao_link").d("카카오링크 보내기 성공: ${linkResult.intent}")
-
- startActivity(linkResult.intent) //카카오톡이 깔려있을 경우 카카오톡으로 넘기기
-
- // 카카오링크 보내기에 성공했지만 아래 경고 메시지가 존재할 경우 일부 컨텐츠가 정상 동작하지 않음
- Timber.tag("kakao_link").d("Warning Msg: ${linkResult.warningMsg}")
- Timber.tag("kakao_link").d("Argument Msg: ${linkResult.argumentMsg}")
- }
- }
- }
- } else { // 카카오톡 미설치: 웹 공유 사용 권장
- // 웹 공유 예시 코드
- val sharerUrl = WebSharerClient.instance.defaultTemplateUri(defaultFeed)
-
- // 1. CustomTabs으로 Chrome 브라우저 열기
- try {
- context?.let { KakaoCustomTabsClient.openWithDefault(it, sharerUrl) }
- } catch (e: UnsupportedOperationException) {
- // Chrome 브라우저가 없을 때
- Toast.makeText(context, "chrome 또는 인터넷 브라우저를 설치해주세요", Toast.LENGTH_SHORT).show()
- }
-
- // 2. CustomTabs으로 디바이스 기본 브라우저 열기
- try {
- context?.let { KakaoCustomTabsClient.open(it, sharerUrl) }
- } catch (e: ActivityNotFoundException) {
- // 인터넷 브라우저가 없을 때
- Toast.makeText(context, "chrome 또는 인터넷 브라우저를 설치해주세요", Toast.LENGTH_SHORT).show()
- }
- }
- }
-
private fun initStartRunButtonClickListener() {
binding.btnCourseDetailStartRun.setOnClickListener {
if (isVisitorMode) {