diff --git a/android/festago/app/src/main/AndroidManifest.xml b/android/festago/app/src/main/AndroidManifest.xml index 15a0d0bb2..140e91a45 100644 --- a/android/festago/app/src/main/AndroidManifest.xml +++ b/android/festago/app/src/main/AndroidManifest.xml @@ -68,7 +68,7 @@ android:exported="false" /> diff --git a/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt b/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt index 1a1877069..8f07fa96d 100644 --- a/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt +++ b/android/festago/app/src/main/java/com/festago/festago/FestagoApplication.kt @@ -1,6 +1,10 @@ package com.festago.festago import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import com.festago.festago.presentation.fcm.FcmMessageType import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.HiltAndroidApp @@ -10,9 +14,21 @@ class FestagoApplication : Application() { override fun onCreate() { super.onCreate() initKakaoSdk() + initNotificationChannel() } private fun initKakaoSdk() { KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY) } + + private fun initNotificationChannel() { + val channel = NotificationChannel( + FcmMessageType.ENTRY_ALERT.channelId, + getString(R.string.entry_alert_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ) + val notificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } } diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt new file mode 100644 index 000000000..6bb066104 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/FcmMessageType.kt @@ -0,0 +1,7 @@ +package com.festago.festago.presentation.fcm + +enum class FcmMessageType(val id: Int, val channelId: String) { + ENTRY_ALERT(id = 0, channelId = "ENTRY_ALERT"), + ENTRY_PROCESS(id = 1, channelId = "ENTRY_PROCESS"), + ; +} diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt new file mode 100644 index 000000000..6abea300b --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/NotificationManager.kt @@ -0,0 +1,46 @@ +package com.festago.festago.presentation.fcm + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.festago.festago.R +import com.festago.festago.presentation.fcm.FcmMessageType.ENTRY_ALERT +import com.festago.festago.presentation.ui.home.HomeActivity +import com.festago.festago.presentation.util.checkNotificationPermission + +class NotificationManager(private val context: Context) { + + private val intent = HomeActivity.getIntent(context).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + + private val pendingIntent = PendingIntent.getActivity( + context, + HOME_REQUEST_CODE, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + + private val entryAlertNotificationBuilder = + NotificationCompat.Builder(context, ENTRY_ALERT.channelId) + .setSmallIcon(R.mipmap.ic_festago_logo_round) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + .setContentIntent(pendingIntent) + + fun sendEntryAlertNotification(title: String, body: String) { + entryAlertNotificationBuilder + .setContentTitle(title) + .setContentText(body) + + checkNotificationPermission(context) { + NotificationManagerCompat.from(context).notify(0, entryAlertNotificationBuilder.build()) + } + } + + companion object { + private const val HOME_REQUEST_CODE = 0 + } +} diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt new file mode 100644 index 000000000..b2061db31 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/fcm/TicketEntryService.kt @@ -0,0 +1,42 @@ +package com.festago.festago.presentation.fcm + +import com.festago.festago.presentation.fcm.FcmMessageType.ENTRY_ALERT +import com.festago.festago.presentation.fcm.FcmMessageType.ENTRY_PROCESS +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.runBlocking + +class TicketEntryService : FirebaseMessagingService() { + + private val notificationManager by lazy { NotificationManager(this) } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + when (remoteMessage.notification?.channelId) { + ENTRY_ALERT.channelId -> handleEntryAlert(remoteMessage) + + ENTRY_PROCESS.channelId -> { + runBlocking { + ticketStateChangeEvent.emit(Unit) + } + } + + else -> Unit + } + } + + private fun handleEntryAlert(remoteMessage: RemoteMessage) { + notificationManager.sendEntryAlertNotification( + remoteMessage.notification?.title ?: "", + remoteMessage.notification?.body ?: "" + ) + } + + override fun onNewToken(token: String) { + // TODO: 토큰이 변경되었을 때 처리 + } + + companion object { + val ticketStateChangeEvent: MutableSharedFlow = MutableSharedFlow() + } +} diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt deleted file mode 100644 index 14abf21a4..000000000 --- a/android/festago/app/src/main/java/com/festago/festago/presentation/service/TicketEntryService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.festago.festago.presentation.service - -import com.google.firebase.messaging.FirebaseMessagingService -import com.google.firebase.messaging.RemoteMessage -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.runBlocking - -class TicketEntryService : FirebaseMessagingService() { - override fun onMessageReceived(remoteMessage: RemoteMessage) { - runBlocking { - // TODO: 입장완료 로직인지 확인하는 로직 추가 필요 - ticketStateChangeEvent.emit(Unit) - } - } - - override fun onNewToken(token: String) { - // TODO: 토큰이 변경되었을 때 처리 - } - - companion object { - val ticketStateChangeEvent: MutableSharedFlow = MutableSharedFlow() - } -} diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt index 3dbc96f79..59dd80bb7 100644 --- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/home/HomeActivity.kt @@ -3,6 +3,7 @@ package com.festago.festago.presentation.ui.home import android.content.Context import android.content.Intent import android.os.Bundle +import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels @@ -15,6 +16,7 @@ import com.festago.festago.presentation.ui.home.mypage.MyPageFragment import com.festago.festago.presentation.ui.home.ticketlist.TicketListFragment import com.festago.festago.presentation.ui.signin.SignInActivity import com.festago.festago.presentation.util.repeatOnStarted +import com.festago.festago.presentation.util.requestNotificationPermission import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -37,6 +39,7 @@ class HomeActivity : AppCompatActivity() { initBinding() initView() initObserve() + initNotificationPermission() } private fun initBinding() { @@ -76,6 +79,21 @@ class HomeActivity : AppCompatActivity() { } } + private fun initNotificationPermission() { + val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (!isGranted) { + Toast.makeText( + this, + getString(R.string.home_notification_permission_denied), + Toast.LENGTH_SHORT + ).show() + } + } + requestNotificationPermission(requestPermissionLauncher) + } + private fun getItemType(menuItemId: Int): HomeItemType { return when (menuItemId) { R.id.item_festival -> HomeItemType.FESTIVAL_LIST diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt index 542aa8132..74094c06b 100644 --- a/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/ui/ticketentry/TicketEntryActivity.kt @@ -9,7 +9,7 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat import com.festago.festago.databinding.ActivityTicketEntryBinding -import com.festago.festago.presentation.service.TicketEntryService +import com.festago.festago.presentation.fcm.TicketEntryService import com.festago.festago.presentation.util.repeatOnStarted import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.BarcodeEncoder diff --git a/android/festago/app/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt b/android/festago/app/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt new file mode 100644 index 000000000..d8f257894 --- /dev/null +++ b/android/festago/app/src/main/java/com/festago/festago/presentation/util/PermissionUtil.kt @@ -0,0 +1,42 @@ +package com.festago.festago.presentation.util + +import android.Manifest.permission.POST_NOTIFICATIONS +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Build +import androidx.activity.result.ActivityResultLauncher +import androidx.core.content.ContextCompat + +fun Activity.requestNotificationPermission(resultLauncher: ActivityResultLauncher) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + checkSelfPermission( + this, + POST_NOTIFICATIONS, + onNotGranted = { + resultLauncher.launch(POST_NOTIFICATIONS) + } + ) + } +} + +fun checkNotificationPermission(context: Context, block: () -> Unit) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + checkSelfPermission(context, POST_NOTIFICATIONS, onGranted = block) + } else { + block() + } +} + +fun checkSelfPermission( + context: Context, + permission: String, + onGranted: () -> Unit = {}, + onNotGranted: () -> Unit = {} +) { + if (ContextCompat.checkSelfPermission(context, permission) == PERMISSION_GRANTED) { + onGranted() + } else { + onNotGranted() + } +} diff --git a/android/festago/app/src/main/res/values/strings.xml b/android/festago/app/src/main/res/values/strings.xml index 3b6ce4bde..35daf2b7f 100644 --- a/android/festago/app/src/main/res/values/strings.xml +++ b/android/festago/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ 축제 목록 티켓 목록 마이페이지 + 알림 권한을 거부했습니다 :( %1s ~ %1s @@ -136,4 +137,7 @@ 학교 목록 불러오기에 실패했습니다. 학교 선택 + + 공연 입장 알림 +