Skip to content

Commit eb82547

Browse files
authored
[MERGE] #364 -> dev
[FEAT/#364] 공고 상세 뷰 / 카카오톡 공유하기
2 parents b723d29 + b279d27 commit eb82547

11 files changed

Lines changed: 146 additions & 54 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
88
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
99

10+
<queries>
11+
<package android:name="com.kakao.talk" />
12+
</queries>
13+
1014
<application
1115
android:name=".MyApp"
1216
android:allowBackup="true"
@@ -33,6 +37,8 @@
3337
<data
3438
android:host="oauth"
3539
android:scheme="kakao${NATIVE_APP_KEY}" />
40+
41+
<data android:host="kakaolink" />
3642
</intent-filter>
3743
</activity>
3844

core/designsystem/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ dependencies {
2424
implementation(libs.lottie)
2525
implementation(libs.coil.compose)
2626
implementation(libs.coil.network.okhttp)
27+
implementation(libs.kakao.share)
2728
}

core/designsystem/src/main/java/com/terning/core/designsystem/component/topappbar/TerningBasicTopAppBar.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ import com.terning.core.designsystem.theme.White
2222
@OptIn(ExperimentalMaterial3Api::class)
2323
@Composable
2424
fun TerningBasicTopAppBar(
25+
modifier: Modifier = Modifier,
2526
title: String = "",
26-
modifier: Modifier,
2727
showBackButton: Boolean = false,
2828
actions: List<@Composable () -> Unit> = emptyList(),
2929
onBackButtonClick: () -> Unit = {},
3030
) {
3131
CenterAlignedTopAppBar(
32+
modifier = modifier,
3233
title = {
3334
Text(
3435
text = title,
@@ -56,7 +57,7 @@ fun TerningBasicTopAppBar(
5657
}
5758
},
5859
actions = {
59-
actions.drop(1).forEach { action ->
60+
actions.forEach { action ->
6061
action()
6162
}
6263
},
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.terning.core.designsystem.util
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import androidx.browser.customtabs.CustomTabsIntent
6+
import com.kakao.sdk.share.ShareClient
7+
import com.kakao.sdk.share.WebSharerClient
8+
import dagger.hilt.android.qualifiers.ApplicationContext
9+
import timber.log.Timber
10+
import javax.inject.Inject
11+
12+
class KakaoUtil @Inject constructor(
13+
@ApplicationContext private val context: Context
14+
) {
15+
private val templateId = 118600L
16+
17+
fun shareToKakaoTalk(templateArgs: Map<String, String>) {
18+
if (ShareClient.instance.isKakaoTalkSharingAvailable(context)) {
19+
ShareClient.instance.shareCustom(
20+
context,
21+
templateId,
22+
templateArgs
23+
) { sharingResult, error ->
24+
if (error != null) {
25+
Timber.e("카카오톡 공유 실패: ${error.message}")
26+
} else if (sharingResult != null) {
27+
context.startActivity(sharingResult.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
28+
}
29+
}
30+
} else {
31+
val sharerUrl = WebSharerClient.instance.makeCustomUrl(templateId, templateArgs)
32+
try {
33+
val intent = CustomTabsIntent.Builder().build().intent
34+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
35+
intent.data = sharerUrl
36+
context.startActivity(intent)
37+
} catch (e: Exception) {
38+
Timber.e("웹 공유 실패: ${e.message}")
39+
}
40+
41+
}
42+
}
43+
}

feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import androidx.compose.foundation.layout.padding
1010
import androidx.compose.foundation.layout.statusBarsPadding
1111
import androidx.compose.foundation.lazy.LazyColumn
1212
import androidx.compose.foundation.text.selection.SelectionContainer
13+
import androidx.compose.material3.Icon
1314
import androidx.compose.material3.Text
1415
import androidx.compose.runtime.Composable
1516
import androidx.compose.runtime.LaunchedEffect
1617
import androidx.compose.runtime.getValue
1718
import androidx.compose.ui.Modifier
1819
import androidx.compose.ui.platform.LocalContext
20+
import androidx.compose.ui.res.painterResource
1921
import androidx.compose.ui.res.stringResource
2022
import androidx.compose.ui.unit.dp
2123
import androidx.core.net.toUri
@@ -27,16 +29,14 @@ import androidx.navigation.NavHostController
2729
import com.terning.core.analytics.EventType
2830
import com.terning.core.analytics.LocalTracker
2931
import com.terning.core.designsystem.component.topappbar.BackButtonTopAppBar
30-
import com.terning.core.designsystem.extension.customShadow
32+
import com.terning.core.designsystem.extension.noRippleClickable
3133
import com.terning.core.designsystem.extension.toast
3234
import com.terning.core.designsystem.state.UiState
3335
import com.terning.core.designsystem.theme.CalRed
34-
import com.terning.core.designsystem.theme.Grey200
3536
import com.terning.core.designsystem.theme.Grey400
3637
import com.terning.core.designsystem.theme.TerningTheme
3738
import com.terning.core.designsystem.theme.White
3839
import com.terning.domain.intern.entity.InternInfo
39-
import com.terning.domain.intern.entity.InternInfo.Companion.EMPTY_INTERN
4040
import com.terning.feature.dialog.cancel.ScrapCancelDialog
4141
import com.terning.feature.dialog.detail.ScrapDialog
4242
import com.terning.feature.intern.component.InternBottomBar
@@ -68,42 +68,51 @@ fun InternRoute(
6868
viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
6969
.collect { sideEffect ->
7070
when (sideEffect) {
71-
is InternViewSideEffect.ShowToast -> context.toast(sideEffect.message)
71+
is InternSideEffect.Toast -> context.toast(sideEffect.message)
7272
}
7373
}
7474
}
7575

76-
77-
val internInfo = (internState.loadState as? UiState.Success<InternInfo>)?.data ?: EMPTY_INTERN
78-
79-
InternScreen(
80-
announcementId = announcementId,
81-
internUiState = internState,
82-
internInfo = internInfo,
83-
navController = navController,
84-
onDismissCancelDialog = {
85-
with(viewModel) {
86-
updateScrapCancelDialogVisibility(false)
87-
getInternInfo(announcementId)
88-
}
89-
},
90-
onDismissScrapDialog = {
91-
with(viewModel) {
92-
updateInternDialogVisibility(false)
93-
getInternInfo(announcementId)
94-
}
95-
},
96-
onClickCancelButton = {
97-
viewModel.updateScrapCancelDialogVisibility(true)
98-
},
99-
onClickScrapButton = {
100-
amplitudeTracker.track(
101-
type = EventType.CLICK,
102-
name = "detail_scrap"
76+
when (internState.loadState) {
77+
UiState.Loading -> {}
78+
UiState.Empty -> {}
79+
is UiState.Failure -> {}
80+
is UiState.Success -> {
81+
InternScreen(
82+
announcementId = announcementId,
83+
internUiState = internState,
84+
internInfo = (internState.loadState as UiState.Success).data,
85+
navController = navController,
86+
onClickShareButton = {
87+
viewModel.onKakaoShareClicked(
88+
(internState.loadState as UiState.Success).data
89+
)
90+
},
91+
onDismissCancelDialog = {
92+
with(viewModel) {
93+
updateScrapCancelDialogVisibility(false)
94+
getInternInfo(announcementId)
95+
}
96+
},
97+
onDismissScrapDialog = {
98+
with(viewModel) {
99+
updateInternDialogVisibility(false)
100+
getInternInfo(announcementId)
101+
}
102+
},
103+
onClickCancelButton = {
104+
viewModel.updateScrapCancelDialogVisibility(true)
105+
},
106+
onClickScrapButton = {
107+
amplitudeTracker.track(
108+
type = EventType.CLICK,
109+
name = "detail_scrap"
110+
)
111+
viewModel.updateInternDialogVisibility(true)
112+
}
103113
)
104-
viewModel.updateInternDialogVisibility(true)
105114
}
106-
)
115+
}
107116
}
108117

109118
@Composable
@@ -113,6 +122,7 @@ fun InternScreen(
113122
navController: NavHostController,
114123
internUiState: InternUiState,
115124
internInfo: InternInfo,
125+
onClickShareButton: () -> Unit,
116126
onDismissCancelDialog: (Boolean) -> Unit,
117127
onDismissScrapDialog: (Boolean) -> Unit,
118128
onClickCancelButton: (InternInfo) -> Unit,
@@ -151,13 +161,20 @@ fun InternScreen(
151161
) {
152162
BackButtonTopAppBar(
153163
title = stringResource(id = R.string.intern_top_app_bar_title),
154-
modifier = Modifier.customShadow(
155-
color = Grey200,
156-
offsetY = 2.dp
157-
),
158164
onBackButtonClick = {
159165
navController.popBackStack()
160166
},
167+
actions = listOf {
168+
Icon(
169+
painter = painterResource(id = R.drawable.ic_share_32),
170+
contentDescription = null,
171+
modifier = Modifier
172+
.padding(end = 8.dp)
173+
.noRippleClickable(
174+
onClick = onClickShareButton
175+
)
176+
)
177+
}
161178
)
162179

163180
LazyColumn(
@@ -289,4 +306,4 @@ fun InternScreen(
289306
)
290307
}
291308
}
292-
}
309+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.terning.feature.intern
2+
3+
import androidx.annotation.StringRes
4+
5+
sealed class InternSideEffect {
6+
data class Toast(@StringRes val message: Int) :
7+
InternSideEffect()
8+
}

feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.terning.feature.intern
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.terning.core.designsystem.state.UiState
6+
import com.terning.core.designsystem.util.KakaoUtil
7+
import com.terning.domain.intern.entity.InternInfo
68
import com.terning.domain.intern.repository.InternRepository
79
import com.terning.feature.intern.model.InternUiState
810
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -17,12 +19,13 @@ import javax.inject.Inject
1719
@HiltViewModel
1820
class InternViewModel @Inject constructor(
1921
private val internRepository: InternRepository,
22+
private val kakaoUtil: KakaoUtil
2023
) : ViewModel() {
2124

2225
private val _internUiState = MutableStateFlow(InternUiState())
2326
val internUiState get() = _internUiState.asStateFlow()
2427

25-
private val _sideEffect: MutableSharedFlow<InternViewSideEffect> = MutableSharedFlow()
28+
private val _sideEffect: MutableSharedFlow<InternSideEffect> = MutableSharedFlow()
2629
val sideEffect = _sideEffect.asSharedFlow()
2730

2831
fun getInternInfo(id: Long) {
@@ -37,7 +40,7 @@ class InternViewModel @Inject constructor(
3740
_internUiState.value = _internUiState.value.copy(
3841
loadState = UiState.Failure(exception.toString())
3942
)
40-
_sideEffect.emit(InternViewSideEffect.ShowToast(R.string.server_failure))
43+
_sideEffect.emit(InternSideEffect.Toast(R.string.server_failure))
4144
}
4245
}
4346
}
@@ -63,4 +66,18 @@ class InternViewModel @Inject constructor(
6366
it.copy(showWeb = show)
6467
}
6568
}
69+
70+
fun onKakaoShareClicked(
71+
internInfo: InternInfo
72+
) {
73+
val templateArgs = mapOf(
74+
"COMPANY_IMG" to internInfo.companyImage,
75+
"TITLE" to internInfo.title,
76+
"DEADLINE" to internInfo.deadline,
77+
"START_DATE" to internInfo.startYearMonth,
78+
"PERIOD" to internInfo.workingPeriod,
79+
"REGI_WEB_DOMAIN" to internInfo.url
80+
)
81+
kakaoUtil.shareToKakaoTalk(templateArgs)
82+
}
6683
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
package com.terning.feature.intern
21

32
import androidx.annotation.StringRes
43

54
sealed class InternViewSideEffect {
6-
data class ShowToast(@StringRes val message: Int) :
5+
data class Toast(@StringRes val message: Int) :
76
InternViewSideEffect()
87
}

feature/intern/src/main/res/drawable/ic_intern_share_22.xml

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="32dp"
3+
android:height="32dp"
4+
android:viewportWidth="32"
5+
android:viewportHeight="32">
6+
<path
7+
android:pathData="M12.71,11.71L15,9.41V19C15,19.265 15.105,19.52 15.293,19.707C15.48,19.895 15.735,20 16,20C16.265,20 16.52,19.895 16.707,19.707C16.895,19.52 17,19.265 17,19V9.41L19.29,11.71C19.383,11.804 19.494,11.878 19.615,11.929C19.737,11.98 19.868,12.006 20,12.006C20.132,12.006 20.263,11.98 20.385,11.929C20.506,11.878 20.617,11.804 20.71,11.71C20.804,11.617 20.878,11.506 20.929,11.385C20.98,11.263 21.006,11.132 21.006,11C21.006,10.868 20.98,10.737 20.929,10.615C20.878,10.494 20.804,10.383 20.71,10.29L16.71,6.29C16.615,6.199 16.503,6.128 16.38,6.08C16.136,5.98 15.863,5.98 15.62,6.08C15.497,6.128 15.385,6.199 15.29,6.29L11.29,10.29C11.197,10.383 11.123,10.494 11.072,10.616C11.022,10.738 10.996,10.868 10.996,11C10.996,11.132 11.022,11.262 11.072,11.384C11.123,11.506 11.197,11.617 11.29,11.71C11.383,11.803 11.494,11.877 11.616,11.928C11.738,11.978 11.868,12.004 12,12.004C12.132,12.004 12.262,11.978 12.384,11.928C12.506,11.877 12.617,11.803 12.71,11.71ZM25,16C24.735,16 24.48,16.105 24.293,16.293C24.105,16.48 24,16.735 24,17V23C24,23.265 23.895,23.52 23.707,23.707C23.52,23.895 23.265,24 23,24H9C8.735,24 8.48,23.895 8.293,23.707C8.105,23.52 8,23.265 8,23V17C8,16.735 7.895,16.48 7.707,16.293C7.52,16.105 7.265,16 7,16C6.735,16 6.48,16.105 6.293,16.293C6.105,16.48 6,16.735 6,17V23C6,23.796 6.316,24.559 6.879,25.121C7.441,25.684 8.204,26 9,26H23C23.796,26 24.559,25.684 25.121,25.121C25.684,24.559 26,23.796 26,23V17C26,16.735 25.895,16.48 25.707,16.293C25.52,16.105 25.265,16 25,16Z"
8+
android:fillColor="#373737"/>
9+
</vector>

0 commit comments

Comments
 (0)