diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 96357aa7f..9a85b9266 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,10 @@ + + + + + + diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index d362f9c2c..9d8529afa 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -24,4 +24,5 @@ dependencies { implementation(libs.lottie) implementation(libs.coil.compose) implementation(libs.coil.network.okhttp) + implementation(libs.kakao.share) } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/terning/core/designsystem/component/topappbar/TerningBasicTopAppBar.kt b/core/designsystem/src/main/java/com/terning/core/designsystem/component/topappbar/TerningBasicTopAppBar.kt index 7d3b1bd7b..074739668 100644 --- a/core/designsystem/src/main/java/com/terning/core/designsystem/component/topappbar/TerningBasicTopAppBar.kt +++ b/core/designsystem/src/main/java/com/terning/core/designsystem/component/topappbar/TerningBasicTopAppBar.kt @@ -22,13 +22,14 @@ import com.terning.core.designsystem.theme.White @OptIn(ExperimentalMaterial3Api::class) @Composable fun TerningBasicTopAppBar( + modifier: Modifier = Modifier, title: String = "", - modifier: Modifier, showBackButton: Boolean = false, actions: List<@Composable () -> Unit> = emptyList(), onBackButtonClick: () -> Unit = {}, ) { CenterAlignedTopAppBar( + modifier = modifier, title = { Text( text = title, @@ -56,7 +57,7 @@ fun TerningBasicTopAppBar( } }, actions = { - actions.drop(1).forEach { action -> + actions.forEach { action -> action() } }, diff --git a/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt b/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt new file mode 100644 index 000000000..267575616 --- /dev/null +++ b/core/designsystem/src/main/java/com/terning/core/designsystem/util/KakaoUtil.kt @@ -0,0 +1,43 @@ +package com.terning.core.designsystem.util + +import android.content.Context +import android.content.Intent +import androidx.browser.customtabs.CustomTabsIntent +import com.kakao.sdk.share.ShareClient +import com.kakao.sdk.share.WebSharerClient +import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber +import javax.inject.Inject + +class KakaoUtil @Inject constructor( + @ApplicationContext private val context: Context +) { + private val templateId = 118600L + + fun shareToKakaoTalk(templateArgs: Map) { + if (ShareClient.instance.isKakaoTalkSharingAvailable(context)) { + ShareClient.instance.shareCustom( + context, + templateId, + templateArgs + ) { sharingResult, error -> + if (error != null) { + Timber.e("카카오톡 공유 실패: ${error.message}") + } else if (sharingResult != null) { + context.startActivity(sharingResult.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } + } + } else { + val sharerUrl = WebSharerClient.instance.makeCustomUrl(templateId, templateArgs) + try { + val intent = CustomTabsIntent.Builder().build().intent + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.data = sharerUrl + context.startActivity(intent) + } catch (e: Exception) { + Timber.e("웹 공유 실패: ${e.message}") + } + + } + } +} diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt index be433456b..7f52a79cf 100644 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternRoute.kt @@ -10,12 +10,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri @@ -27,16 +29,14 @@ import androidx.navigation.NavHostController import com.terning.core.analytics.EventType import com.terning.core.analytics.LocalTracker import com.terning.core.designsystem.component.topappbar.BackButtonTopAppBar -import com.terning.core.designsystem.extension.customShadow +import com.terning.core.designsystem.extension.noRippleClickable import com.terning.core.designsystem.extension.toast import com.terning.core.designsystem.state.UiState import com.terning.core.designsystem.theme.CalRed -import com.terning.core.designsystem.theme.Grey200 import com.terning.core.designsystem.theme.Grey400 import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.designsystem.theme.White import com.terning.domain.intern.entity.InternInfo -import com.terning.domain.intern.entity.InternInfo.Companion.EMPTY_INTERN import com.terning.feature.dialog.cancel.ScrapCancelDialog import com.terning.feature.dialog.detail.ScrapDialog import com.terning.feature.intern.component.InternBottomBar @@ -68,42 +68,51 @@ fun InternRoute( viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle) .collect { sideEffect -> when (sideEffect) { - is InternViewSideEffect.ShowToast -> context.toast(sideEffect.message) + is InternSideEffect.Toast -> context.toast(sideEffect.message) } } } - - val internInfo = (internState.loadState as? UiState.Success)?.data ?: EMPTY_INTERN - - InternScreen( - announcementId = announcementId, - internUiState = internState, - internInfo = internInfo, - navController = navController, - onDismissCancelDialog = { - with(viewModel) { - updateScrapCancelDialogVisibility(false) - getInternInfo(announcementId) - } - }, - onDismissScrapDialog = { - with(viewModel) { - updateInternDialogVisibility(false) - getInternInfo(announcementId) - } - }, - onClickCancelButton = { - viewModel.updateScrapCancelDialogVisibility(true) - }, - onClickScrapButton = { - amplitudeTracker.track( - type = EventType.CLICK, - name = "detail_scrap" + when (internState.loadState) { + UiState.Loading -> {} + UiState.Empty -> {} + is UiState.Failure -> {} + is UiState.Success -> { + InternScreen( + announcementId = announcementId, + internUiState = internState, + internInfo = (internState.loadState as UiState.Success).data, + navController = navController, + onClickShareButton = { + viewModel.onKakaoShareClicked( + (internState.loadState as UiState.Success).data + ) + }, + onDismissCancelDialog = { + with(viewModel) { + updateScrapCancelDialogVisibility(false) + getInternInfo(announcementId) + } + }, + onDismissScrapDialog = { + with(viewModel) { + updateInternDialogVisibility(false) + getInternInfo(announcementId) + } + }, + onClickCancelButton = { + viewModel.updateScrapCancelDialogVisibility(true) + }, + onClickScrapButton = { + amplitudeTracker.track( + type = EventType.CLICK, + name = "detail_scrap" + ) + viewModel.updateInternDialogVisibility(true) + } ) - viewModel.updateInternDialogVisibility(true) } - ) + } } @Composable @@ -113,6 +122,7 @@ fun InternScreen( navController: NavHostController, internUiState: InternUiState, internInfo: InternInfo, + onClickShareButton: () -> Unit, onDismissCancelDialog: (Boolean) -> Unit, onDismissScrapDialog: (Boolean) -> Unit, onClickCancelButton: (InternInfo) -> Unit, @@ -151,13 +161,20 @@ fun InternScreen( ) { BackButtonTopAppBar( title = stringResource(id = R.string.intern_top_app_bar_title), - modifier = Modifier.customShadow( - color = Grey200, - offsetY = 2.dp - ), onBackButtonClick = { navController.popBackStack() }, + actions = listOf { + Icon( + painter = painterResource(id = R.drawable.ic_share_32), + contentDescription = null, + modifier = Modifier + .padding(end = 8.dp) + .noRippleClickable( + onClick = onClickShareButton + ) + ) + } ) LazyColumn( @@ -289,4 +306,4 @@ fun InternScreen( ) } } -} \ No newline at end of file +} diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternSideEffect.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternSideEffect.kt new file mode 100644 index 000000000..4f37bd12c --- /dev/null +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternSideEffect.kt @@ -0,0 +1,8 @@ +package com.terning.feature.intern + +import androidx.annotation.StringRes + +sealed class InternSideEffect { + data class Toast(@StringRes val message: Int) : + InternSideEffect() +} \ No newline at end of file diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt index 98bd12647..05dbd5df8 100644 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternViewModel.kt @@ -3,6 +3,8 @@ package com.terning.feature.intern import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.terning.core.designsystem.state.UiState +import com.terning.core.designsystem.util.KakaoUtil +import com.terning.domain.intern.entity.InternInfo import com.terning.domain.intern.repository.InternRepository import com.terning.feature.intern.model.InternUiState import dagger.hilt.android.lifecycle.HiltViewModel @@ -17,12 +19,13 @@ import javax.inject.Inject @HiltViewModel class InternViewModel @Inject constructor( private val internRepository: InternRepository, + private val kakaoUtil: KakaoUtil ) : ViewModel() { private val _internUiState = MutableStateFlow(InternUiState()) val internUiState get() = _internUiState.asStateFlow() - private val _sideEffect: MutableSharedFlow = MutableSharedFlow() + private val _sideEffect: MutableSharedFlow = MutableSharedFlow() val sideEffect = _sideEffect.asSharedFlow() fun getInternInfo(id: Long) { @@ -37,7 +40,7 @@ class InternViewModel @Inject constructor( _internUiState.value = _internUiState.value.copy( loadState = UiState.Failure(exception.toString()) ) - _sideEffect.emit(InternViewSideEffect.ShowToast(R.string.server_failure)) + _sideEffect.emit(InternSideEffect.Toast(R.string.server_failure)) } } } @@ -63,4 +66,18 @@ class InternViewModel @Inject constructor( it.copy(showWeb = show) } } + + fun onKakaoShareClicked( + internInfo: InternInfo + ) { + val templateArgs = mapOf( + "COMPANY_IMG" to internInfo.companyImage, + "TITLE" to internInfo.title, + "DEADLINE" to internInfo.deadline, + "START_DATE" to internInfo.startYearMonth, + "PERIOD" to internInfo.workingPeriod, + "REGI_WEB_DOMAIN" to internInfo.url + ) + kakaoUtil.shareToKakaoTalk(templateArgs) + } } diff --git a/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt b/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt index b6525a55b..9954265af 100644 --- a/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt +++ b/feature/intern/src/main/java/com/terning/feature/intern/InternViewSideEffect.kt @@ -1,8 +1,7 @@ -package com.terning.feature.intern import androidx.annotation.StringRes sealed class InternViewSideEffect { - data class ShowToast(@StringRes val message: Int) : + data class Toast(@StringRes val message: Int) : InternViewSideEffect() } \ No newline at end of file diff --git a/feature/intern/src/main/res/drawable/ic_intern_share_22.xml b/feature/intern/src/main/res/drawable/ic_intern_share_22.xml deleted file mode 100644 index db0f653f7..000000000 --- a/feature/intern/src/main/res/drawable/ic_intern_share_22.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/feature/intern/src/main/res/drawable/ic_share_32.xml b/feature/intern/src/main/res/drawable/ic_share_32.xml new file mode 100644 index 000000000..64f9540cd --- /dev/null +++ b/feature/intern/src/main/res/drawable/ic_share_32.xml @@ -0,0 +1,9 @@ + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e64d297f4..c6d7b7741 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -203,6 +203,7 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } lottie = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottieVersion" } kakao-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoVersion" } +kakao-share = { group = "com.kakao.sdk", name = "v2-share", version.ref = "kakaoVersion" } process-phoenix = { group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix" } accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }