Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 코스 상세 페이지 / Firebase 동적 링크로 코스 공유 구현 #326

Merged
merged 3 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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<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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고차함수를 이렇게 활용할 수 있군요! 가독성 향상된 거 같아서 좋습니다!

}
}
}

private fun getCourseDetail() {
Expand Down Expand Up @@ -163,7 +167,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 @@ -238,9 +242,42 @@ class CourseDetailActivity :
}
}

private fun sendFirebaseDynamicLink(title: String, desc: String, image: String) {
val link = "https://rnnt.page.link/?courseId=$publicCourseId"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문자열 리소스 사용하거나 companion 객체 안에 상수화 시켜주는 게 좋을 거 같습니다! (오타 발생 가능성)


FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLink(Uri.parse(link))
.setDomainUriPrefix("https://rnnt.page.link")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 문자열도 상수화 시켜주면 좋을 거 같아요!

.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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 상수화 시켜주면 더욱 좋겠네요 :)

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 @@ -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) {
Expand Down
Loading