Skip to content

Commit

Permalink
merge: 행사 카카오 공유 기능 (#888)
Browse files Browse the repository at this point in the history
Related to: #839
  • Loading branch information
tmdgh1592 authored Jan 28, 2024
1 parent 1861824 commit d1d3dcc
Show file tree
Hide file tree
Showing 62 changed files with 486 additions and 316 deletions.
19 changes: 14 additions & 5 deletions android/2023-emmsale/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ android {

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField(
"String",
"GITHUB_CLIENT_ID",
getApiKey("GH_CLIENT_ID"),
)
buildConfigField("String", "GITHUB_CLIENT_ID", getApiKey("GH_CLIENT_ID"))
resValue("string", "kakao_app_key", getApiKey("KAKAO_APP_KEY"))
resValue("string", "kakao_scheme", getApiKey("KAKAO_SCHEME"))
resValue("string", "kakao_host", getApiKey("KAKAO_HOST"))
}

buildFeatures {
buildConfig = true
}

buildTypes {
debug {
buildConfigField("String", "BASE_URL", "\"https://dev.kerdy.kro.kr\"")
Expand All @@ -59,16 +60,20 @@ android {
signingConfig = signingConfigs.getByName("debug")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}

dataBinding {
enable = true
}

tasks.withType(Test::class) {
testLogging {
events.addAll(
Expand All @@ -80,6 +85,7 @@ android {
)
}
}

testOptions {
unitTests.isReturnDefaultValues = true
unitTests.isIncludeAndroidResources = true
Expand Down Expand Up @@ -171,6 +177,9 @@ dependencies {
implementation("androidx.room:room-runtime:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")

// Kakao Share
implementation("com.kakao.sdk:v2-share:2.12.0")
}

kapt {
Expand Down
7 changes: 7 additions & 0 deletions android/2023-emmsale/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@

# ApiResponse 클래스의 타입 매개변수를 유지하기 위해 추가. 안하면 CallAdapter에서 retrofit2.Call<ApiResponse>를 반환하는 CallAdapter 만들 수 없음.
-keepnames, allowobfuscation class com.emmsale.data.common.retrofit.callAdapter.ApiResponse

# 카카오 공유하기
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
-keep interface com.kakao.sdk.**.*Api
-keep class com.kakao.sdk.**.model.* { <fields>; }
-keep class * extends com.google.gson.TypeAdapter
15 changes: 14 additions & 1 deletion android/2023-emmsale/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,20 @@
android:name=".presentation.ui.main.MainActivity"
android:launchMode="singleTask" />
<activity android:name=".presentation.ui.onboarding.OnboardingActivity" />
<activity android:name=".presentation.ui.eventDetail.EventDetailActivity" />
<activity
android:name=".presentation.ui.eventDetail.EventDetailActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="@string/kakao_host"
android:scheme="@string/kakao_scheme" />
</intent-filter>
</activity>
<activity
android:name=".presentation.ui.login.LoginActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.emmsale.presentation

import android.app.Application
import com.emmsale.R
import com.emmsale.presentation.common.KerdyNotificationChannel
import com.google.firebase.analytics.FirebaseAnalytics
import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
Expand All @@ -11,6 +13,7 @@ class KerdyApplication : Application() {
super.onCreate()
initFirebaseAnalytics()
initNotificationChannels()
KakaoSdk.init(this, getString(R.string.kakao_app_key))
}

private fun initFirebaseAnalytics() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract class RefreshableViewModel : NetworkViewModel() {
onSuccess: suspend (T) -> Unit = {},
onFailure: suspend (code: Int, message: String?) -> Unit = { _, _ -> dispatchFetchFailEvent() },
onLoading: suspend () -> Unit = {},
onNetworkError: suspend () -> Unit = {},
onNetworkError: suspend () -> Unit = { dispatchNetworkErrorEvent() },
onStart: suspend () -> Unit = {},
onFinish: suspend () -> Unit = {},
): Job = requestNetwork(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class EventDetailActivity :

registerScreen(this)

fetchEvent()
setupDataBinding()
setupFragmentStateAdapter()
setupBackPressedDispatcher()
Expand All @@ -44,6 +45,14 @@ class EventDetailActivity :
observeUiEvent()
}

private fun fetchEvent() {
val eventId = intent.data?.getQueryParameter(EVENT_ID_KEY)?.toLong()
if (eventId != null) viewModel.eventId = eventId

viewModel.fetchEvent()
viewModel.fetchIsScrapped()
}

private fun setupDataBinding() {
binding.vm = viewModel
binding.navigateToUrl = ::navigateToUrl
Expand Down Expand Up @@ -79,7 +88,6 @@ class EventDetailActivity :
TabLayoutMediator(binding.tablayoutEventdetail, binding.vpEventdetail) { tab, position ->
tab.text = tabNames[position]
}.attach()
binding.vpEventdetail.isUserInputEnabled = false
}

private fun setupBackPressedDispatcher() {
Expand All @@ -96,6 +104,10 @@ class EventDetailActivity :

private fun setupToolbar() {
binding.tbEventdetail.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
binding.tbEventdetail.setOnMenuItemClickListener { item ->
if (item.itemId == R.id.share_event) viewModel.shareEvent()
true
}
}

private fun setupOnTabSelectedListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.emmsale.presentation.base.RefreshableViewModel
import com.emmsale.presentation.common.livedata.NotNullLiveData
import com.emmsale.presentation.common.livedata.NotNullMutableLiveData
import com.emmsale.presentation.common.livedata.SingleLiveEvent
import com.emmsale.presentation.ui.eventDetail.eventSharer.EventSharer
import com.emmsale.presentation.ui.eventDetail.eventSharer.EventTemplateMaker
import com.emmsale.presentation.ui.eventDetail.uiState.EventDetailScreenUiState
import com.emmsale.presentation.ui.eventDetail.uiState.EventDetailUiEvent
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -24,8 +26,10 @@ class EventDetailViewModel @Inject constructor(
stateHandle: SavedStateHandle,
private val eventRepository: EventRepository,
private val recruitmentRepository: RecruitmentRepository,
private val eventTemplateMaker: EventTemplateMaker,
private val eventSharer: EventSharer,
) : RefreshableViewModel() {
val eventId = stateHandle[EVENT_ID_KEY] ?: DEFAULT_EVENT_ID
var eventId = stateHandle[EVENT_ID_KEY] ?: DEFAULT_EVENT_ID

private val _event = NotNullMutableLiveData(Event())
val event: NotNullLiveData<Event> = _event
Expand All @@ -51,17 +55,12 @@ class EventDetailViewModel @Inject constructor(
private val _uiEvent = SingleLiveEvent<EventDetailUiEvent>()
val uiEvent: LiveData<EventDetailUiEvent> = _uiEvent

init {
fetchEvent()
fetchIsScrapped()
}

private fun fetchEvent(): Job = fetchData(
fun fetchEvent(): Job = fetchData(
fetchData = { eventRepository.getEventDetail(eventId) },
onSuccess = { _event.value = it },
)

private fun fetchIsScrapped(): Job = viewModelScope.launch {
fun fetchIsScrapped(): Job = viewModelScope.launch {
when (val result = eventRepository.isScraped(eventId)) {
is Success -> _isScraped.value = result.data
else -> {}
Expand Down Expand Up @@ -117,6 +116,15 @@ class EventDetailViewModel @Inject constructor(
onFinish = { _canStartToWriteRecruitment.value = true },
)

fun shareEvent() {
val eventShareTemplate = eventTemplateMaker.create(
eventId = eventId,
eventName = event.value.name,
posterUrl = event.value.posterImageUrl,
)
eventSharer.shareEvent(eventShareTemplate)
}

companion object {
const val EVENT_ID_KEY = "EVENT_ID_KEY"
private const val DEFAULT_EVENT_ID = -1L
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.emmsale.presentation.ui.eventDetail.eventSharer

import android.content.Context
import com.emmsale.R
import com.emmsale.presentation.common.extension.showToast
import com.kakao.sdk.common.util.KakaoCustomTabsClient
import com.kakao.sdk.share.ShareClient
import com.kakao.sdk.share.WebSharerClient
import com.kakao.sdk.template.model.FeedTemplate
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class EventSharer @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val isNotKakaoTalkInstalled
get() = !ShareClient.instance.isKakaoTalkSharingAvailable(context)

fun shareEvent(eventTemplate: FeedTemplate) {
if (isNotKakaoTalkInstalled) {
handleKakaoNotInstalledError(eventTemplate)
return
}

share(eventTemplate)
}

private fun handleKakaoNotInstalledError(eventTemplate: FeedTemplate) {
val sharerUrl = WebSharerClient.instance.makeDefaultUrl(eventTemplate)

runCatching {
KakaoCustomTabsClient.openWithDefault(context, sharerUrl)
}.onFailure {
context.showToast(R.string.eventdetail_no_exist_browser)
}

runCatching {
KakaoCustomTabsClient.open(context, sharerUrl)
}.onFailure {
context.showToast(R.string.eventdetail_no_exist_browser)
}
}

private fun share(eventTemplate: FeedTemplate) {
ShareClient.instance.shareDefault(context, eventTemplate) { sharingResult, error ->
if (error != null) {
context.showToast(R.string.eventdetail_kakao_share_fail)
} else if (sharingResult != null) {
context.startActivity(sharingResult.intent)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.emmsale.presentation.ui.eventDetail.eventSharer

import android.content.Context
import com.emmsale.R
import com.emmsale.presentation.ui.eventDetail.EventDetailViewModel
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 dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class EventTemplateMaker @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun create(
eventId: Long,
eventName: String,
posterUrl: String?,
): FeedTemplate = FeedTemplate(
content = Content(
title = eventName,
description = context.getString(R.string.eventdetail_share_template_description),
imageUrl = posterUrl ?: "",
link = Link(androidExecutionParams = mapOf(EventDetailViewModel.EVENT_ID_KEY to eventId.toString())),
),
buttons = listOf(
Button(
context.getString(R.string.eventdetail_share_button_title),
Link(androidExecutionParams = mapOf(EventDetailViewModel.EVENT_ID_KEY to eventId.toString())),
),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class FeedDetailViewHolder(
parent: ViewGroup,
onAuthorImageClick: (authorId: Long) -> Unit,
) : FeedOrCommentViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_feeddetail_feed_detail, parent, false),
LayoutInflater.from(parent.context)
.inflate(R.layout.item_feeddetail_feed_detail, parent, false),
) {
private val binding = ItemFeeddetailFeedDetailBinding.bind(itemView)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class NotificationTagConfigViewModel @Inject constructor(
_networkUiEvent.value = NetworkUiEvent.Unexpected(eventTagsResult.error.toString())

interestTagsResult is Unexpected ->
_networkUiEvent.value = NetworkUiEvent.Unexpected(interestTagsResult.error.toString())
_networkUiEvent.value =
NetworkUiEvent.Unexpected(interestTagsResult.error.toString())

eventTagsResult is Failure || interestTagsResult is Failure -> dispatchFetchFailEvent()
eventTagsResult is NetworkError || interestTagsResult is NetworkError -> {
Expand Down Expand Up @@ -93,7 +94,8 @@ class NotificationTagConfigViewModel @Inject constructor(
_networkUiEvent.value = NetworkUiEvent.Unexpected(eventTagsResult.error.toString())

interestTagsResult is Unexpected ->
_networkUiEvent.value = NetworkUiEvent.Unexpected(interestTagsResult.error.toString())
_networkUiEvent.value =
NetworkUiEvent.Unexpected(interestTagsResult.error.toString())

eventTagsResult is Failure || interestTagsResult is Failure -> dispatchFetchFailEvent()
eventTagsResult is NetworkError || interestTagsResult is NetworkError -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class SettingFragment :
{ MyCommentsActivity.startActivity(requireContext()) }
binding.onNotificationSettingButtonClick =
{ NotificationConfigActivity.startActivity(requireContext()) }
binding.onBlockMembersButtonClick = { BlockedMembersActivity.startActivity(requireContext()) }
binding.onBlockMembersButtonClick =
{ BlockedMembersActivity.startActivity(requireContext()) }
binding.onUseTermButtonClick = { UseTermWebViewActivity.startActivity(requireContext()) }
binding.onLogoutButtonClick = ::showLogoutConfirmDialog
binding.onInquirePageButtonClick = ::navigateToInquirePage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M11.354,8.354C11.549,8.158 11.549,7.842 11.354,7.646L8.172,4.464C7.976,4.269 7.66,4.269 7.464,4.464C7.269,4.66 7.269,4.976 7.464,5.172L10.293,8L7.464,10.828C7.269,11.024 7.269,11.34 7.464,11.535C7.66,11.731 7.976,11.731 8.172,11.535L11.354,8.354ZM0.5,0V6H1.5V0H0.5ZM3,8.5H11V7.5H3V8.5ZM0.5,6C0.5,7.381 1.619,8.5 3,8.5V7.5C2.172,7.5 1.5,6.828 1.5,6H0.5Z"
android:fillColor="#D9D9D9" />
android:fillColor="#D9D9D9"
android:pathData="M11.354,8.354C11.549,8.158 11.549,7.842 11.354,7.646L8.172,4.464C7.976,4.269 7.66,4.269 7.464,4.464C7.269,4.66 7.269,4.976 7.464,5.172L10.293,8L7.464,10.828C7.269,11.024 7.269,11.34 7.464,11.535C7.66,11.731 7.976,11.731 8.172,11.535L11.354,8.354ZM0.5,0V6H1.5V0H0.5ZM3,8.5H11V7.5H3V8.5ZM0.5,6C0.5,7.381 1.619,8.5 3,8.5V7.5C2.172,7.5 1.5,6.828 1.5,6H0.5Z" />
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
android:viewportWidth="18"
android:viewportHeight="16">
<path
android:pathData="M5.312,1.613C5.54,1.271 5.849,0.99 6.212,0.796C6.575,0.602 6.98,0.5 7.392,0.5H10.608C11.02,0.5 11.425,0.602 11.788,0.796C12.151,0.99 12.46,1.271 12.688,1.613L13.365,2.629C13.441,2.743 13.544,2.837 13.665,2.901C13.786,2.966 13.921,3 14.058,3H14.833C15.497,3 16.132,3.263 16.601,3.732C17.07,4.201 17.333,4.837 17.333,5.5V13C17.333,13.663 17.07,14.299 16.601,14.768C16.132,15.237 15.497,15.5 14.833,15.5H3.167C2.504,15.5 1.868,15.237 1.399,14.768C0.93,14.299 0.667,13.663 0.667,13V5.5C0.667,4.837 0.93,4.201 1.399,3.732C1.868,3.263 2.504,3 3.167,3H3.942C4.079,3 4.214,2.966 4.335,2.901C4.456,2.837 4.559,2.743 4.635,2.629L5.312,1.613ZM7.333,8.833C7.333,8.391 7.509,7.967 7.822,7.655C8.134,7.342 8.558,7.167 9,7.167C9.442,7.167 9.866,7.342 10.179,7.655C10.491,7.967 10.667,8.391 10.667,8.833C10.667,9.275 10.491,9.699 10.179,10.012C9.866,10.324 9.442,10.5 9,10.5C8.558,10.5 8.134,10.324 7.822,10.012C7.509,9.699 7.333,9.275 7.333,8.833ZM9,5.5C8.116,5.5 7.268,5.851 6.643,6.476C6.018,7.101 5.667,7.949 5.667,8.833C5.667,9.717 6.018,10.565 6.643,11.19C7.268,11.816 8.116,12.167 9,12.167C9.884,12.167 10.732,11.816 11.357,11.19C11.982,10.565 12.333,9.717 12.333,8.833C12.333,7.949 11.982,7.101 11.357,6.476C10.732,5.851 9.884,5.5 9,5.5Z"
android:fillColor="#545454"
android:fillType="evenOdd" />
android:fillType="evenOdd"
android:pathData="M5.312,1.613C5.54,1.271 5.849,0.99 6.212,0.796C6.575,0.602 6.98,0.5 7.392,0.5H10.608C11.02,0.5 11.425,0.602 11.788,0.796C12.151,0.99 12.46,1.271 12.688,1.613L13.365,2.629C13.441,2.743 13.544,2.837 13.665,2.901C13.786,2.966 13.921,3 14.058,3H14.833C15.497,3 16.132,3.263 16.601,3.732C17.07,4.201 17.333,4.837 17.333,5.5V13C17.333,13.663 17.07,14.299 16.601,14.768C16.132,15.237 15.497,15.5 14.833,15.5H3.167C2.504,15.5 1.868,15.237 1.399,14.768C0.93,14.299 0.667,13.663 0.667,13V5.5C0.667,4.837 0.93,4.201 1.399,3.732C1.868,3.263 2.504,3 3.167,3H3.942C4.079,3 4.214,2.966 4.335,2.901C4.456,2.837 4.559,2.743 4.635,2.629L5.312,1.613ZM7.333,8.833C7.333,8.391 7.509,7.967 7.822,7.655C8.134,7.342 8.558,7.167 9,7.167C9.442,7.167 9.866,7.342 10.179,7.655C10.491,7.967 10.667,8.391 10.667,8.833C10.667,9.275 10.491,9.699 10.179,10.012C9.866,10.324 9.442,10.5 9,10.5C8.558,10.5 8.134,10.324 7.822,10.012C7.509,9.699 7.333,9.275 7.333,8.833ZM9,5.5C8.116,5.5 7.268,5.851 6.643,6.476C6.018,7.101 5.667,7.949 5.667,8.833C5.667,9.717 6.018,10.565 6.643,11.19C7.268,11.816 8.116,12.167 9,12.167C9.884,12.167 10.732,11.816 11.357,11.19C11.982,10.565 12.333,9.717 12.333,8.833C12.333,7.949 11.982,7.101 11.357,6.476C10.732,5.851 9.884,5.5 9,5.5Z" />
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="20dp"
android:viewportWidth="16"
android:viewportHeight="20">
<path
android:fillColor="#D9D9D9"
android:pathData="M7,13H9V4H11L8,0L5,4H7V13ZM15,7H12V9H14V18H2V9H4V7H1C0.735,7 0.48,7.105 0.293,7.293C0.105,7.48 0,7.735 0,8V19C0,19.265 0.105,19.52 0.293,19.707C0.48,19.895 0.735,20 1,20H15C15.265,20 15.52,19.895 15.707,19.707C15.895,19.52 16,19.265 16,19V8C16,7.735 15.895,7.48 15.707,7.293C15.52,7.105 15.265,7 15,7Z" />
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M11.807,2.693C12.067,2.433 12.067,2 11.807,1.753L10.247,0.193C10,-0.067 9.567,-0.067 9.307,0.193L8.08,1.413L10.58,3.913M0,9.5V12H2.5L9.873,4.62L7.373,2.12L0,9.5Z"
android:fillColor="#ffffff" />
android:fillColor="#ffffff"
android:pathData="M11.807,2.693C12.067,2.433 12.067,2 11.807,1.753L10.247,0.193C10,-0.067 9.567,-0.067 9.307,0.193L8.08,1.413L10.58,3.913M0,9.5V12H2.5L9.873,4.62L7.373,2.12L0,9.5Z" />
</vector>
Loading

0 comments on commit d1d3dcc

Please sign in to comment.