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) {