Skip to content

Commit

Permalink
Merge pull request #326 from Runnect/feature/feat-dynamic-link
Browse files Browse the repository at this point in the history
[FEAT] 코스 상세 페이지 / Firebase 동적 링크로 코스 공유 구현
  • Loading branch information
unam98 authored Feb 16, 2024
2 parents e204da4 + 1cd2c95 commit 7eff342
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 114 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,9 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<!-- "kakao${YOUR_NATIVE_APP_KEY}://kakaolink" 형식의 앱 실행 스킴을 설정하는데 사용 -->
<data
android:host="kakaolink"
android:scheme="@string/kakao_redirection_scheme" />
android:host="rnnt.page.link"
android:scheme="https" />
</intent-filter>
</activity>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
//토큰 재발급
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -89,33 +82,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<CourseDetailRootScreen>(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() {
Expand Down Expand Up @@ -162,7 +166,7 @@ class CourseDetailActivity :
intent?.let { newIntent ->
newIntent.getCompatibleSerializableExtra<CourseDetailRootScreen>(EXTRA_ROOT_SCREEN)
?.let { rootScreen = it }
publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, 0)
publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, -1)
getCourseDetail()
}
}
Expand Down Expand Up @@ -231,9 +235,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
Expand All @@ -248,71 +285,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) {
Expand Down

0 comments on commit 7eff342

Please sign in to comment.