Skip to content

Commit

Permalink
[FEAT] 푸시알림 클릭 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
kim0hoon committed Oct 25, 2023
1 parent 167978b commit e4419b1
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 2 deletions.
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<activity
android:name="com.polzzak_android.presentation.feature.root.MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -51,6 +52,14 @@
android:scheme="kakao${KAKAO_NATIVE_APP_KEY}" />
</intent-filter>
</activity>

<service
android:name=".presentation.common.service.PolzzakFirebaseMessagingService"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package com.polzzak_android.presentation.common.service

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.polzzak_android.R
import com.polzzak_android.common.util.safeLet
import com.polzzak_android.presentation.feature.root.MainActivity
import timber.log.Timber

class PolzzakFirebaseMessagingService : FirebaseMessagingService() {
Expand All @@ -11,9 +23,53 @@ class PolzzakFirebaseMessagingService : FirebaseMessagingService() {
Timber.d("onNewToken : $token")
}

//TODO 알림 데이터 적용
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
Timber.d("onMessageReceived : $message")
//TOOD 푸쉬알림 받을 경우
Timber.d("onMessageReceived : ${message.toString()}")
Timber.d("onMessageReceived\n data : ${message.data} notification : ${message.notification?.body} title : ${message.notification?.title} ${message.notification?.icon}")
safeLet(message.data["title"], message.data["body"]) { title, content ->
notify("t1", "t2")
}
}

//TODO 알림 분기처리 구현, small icon 앱아이콘 적용, 로그인 정보 저장(access token 등)
private fun notify(title: String, content: String) {
val notificationManager = NotificationManagerCompat.from(applicationContext)
val intent = Intent(applicationContext, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
applicationContext,
PENDING_INTENT_REQUEST_CODE,
intent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
val notificationBuilder = NotificationCompat.Builder(
applicationContext,
NOTIFICATION_CHANNEL_ID
)
val notification =
notificationBuilder.setContentTitle(title)
.setSmallIcon(R.drawable.ic_launcher_background).setContentText(content)
.setContentIntent(pendingIntent).setAutoCancel(true).build()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) return
notificationManager.notify(NOTIFICATION_ID, notification)
}

companion object {
private const val NOTIFICATION_CHANNEL_NAME = "push alarm"
private const val NOTIFICATION_CHANNEL_ID = "push_notification_id"
private const val NOTIFICATION_ID = 1
private const val PENDING_INTENT_REQUEST_CODE = 1000
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.polzzak_android.presentation.feature.root

import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
Expand Down Expand Up @@ -87,6 +88,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), SocialLoginManager {
clearBackPressedEvent()
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
supportFragmentManager.setFragmentResult(NOTIFICATION_INTENT_REQUEST_KEY, Bundle())
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(SAVE_INSTANCE_ACCESS_TOKEN_KEY, getAccessToken())
Expand Down Expand Up @@ -151,5 +157,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), SocialLoginManager {
companion object {
private const val BACK_BTN_DEBOUNCE_TIMER = 3000
private const val SAVE_INSTANCE_ACCESS_TOKEN_KEY = "save_instance_access_token_key"
const val NOTIFICATION_INTENT_REQUEST_KEY = "notification_intent_request_key"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.polzzak_android.presentation.feature.root.host

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class HostViewModel : ViewModel() {
private val _hasNewNotificationLiveData = MutableLiveData<Boolean>()
val hasNewNotificationLiveData: LiveData<Boolean> = _hasNewNotificationLiveData

private var requestHasNewNotificationJob: Job? = null
var isSelectedNotificationTab: Boolean = false

//TODO 새 알림 존재 여부 api 호출
fun requestHasNewNotification() {
if (requestHasNewNotificationJob?.isCompleted == false) return
requestHasNewNotificationJob = viewModelScope.launch {
_hasNewNotificationLiveData.value = (0..1).random()==0
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.polzzak_android.presentation.feature.root.host

import android.graphics.PorterDuff
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.content.ContextCompat
import androidx.core.view.MenuItemCompat
import androidx.core.view.children
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import com.polzzak_android.R
import com.polzzak_android.databinding.FragmentKidHostBinding
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.presentation.feature.root.MainActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
Expand All @@ -17,17 +23,25 @@ class KidHostFragment : BaseFragment<FragmentKidHostBinding>() {

private lateinit var navController: NavController

private val hostViewModel by viewModels<HostViewModel>()

// 시스템 back 버튼 동작 가로치개 위한 callback
private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
navController.popBackStack()
}
}

override fun onResume() {
super.onResume()
hostViewModel.requestHasNewNotification()
}

override fun initView() {
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, backPressedCallback)

setupNavigationView()
initFragmentResultListener()
}

private fun setupNavigationView() {
Expand All @@ -40,12 +54,48 @@ class KidHostFragment : BaseFragment<FragmentKidHostBinding>() {
destination.parent?.findStartDestination()?.id -> {
backPressedCallback.isEnabled = false
binding.kidBtmNav.visibility = View.VISIBLE

if (destination.id != R.id.kidNotificationFragment && hostViewModel.isSelectedNotificationTab) {
hostViewModel.requestHasNewNotification()
}
hostViewModel.isSelectedNotificationTab =
(destination.id == R.id.kidNotificationFragment)
}

else -> {
backPressedCallback.isEnabled = true
binding.kidBtmNav.visibility = View.GONE
}
}
}
}

override fun initObserver() {
super.initObserver()
hostViewModel.hasNewNotificationLiveData.observe(viewLifecycleOwner) {
val context = requireContext()
val notificationMenuItem =
binding.kidBtmNav.menu.children.find { menuItem -> menuItem.itemId == R.id.kid_notification_nav_graph }
notificationMenuItem?.let { menuItem ->
if (it) {
MenuItemCompat.setIconTintMode(menuItem, PorterDuff.Mode.DST)
menuItem.icon =
ContextCompat.getDrawable(context, R.drawable.selector_has_notification)
} else {
MenuItemCompat.setIconTintMode(menuItem, null)
menuItem.icon =
ContextCompat.getDrawable(context, R.drawable.ic_menu_notification)
}
}
}
}

private fun initFragmentResultListener() {
activity?.supportFragmentManager?.setFragmentResultListener(
MainActivity.NOTIFICATION_INTENT_REQUEST_KEY,
viewLifecycleOwner
) { _, _ ->
binding.kidBtmNav.selectedItemId = R.id.kid_notification_nav_graph
}
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
package com.polzzak_android.presentation.feature.root.host

import android.graphics.PorterDuff
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.content.ContextCompat
import androidx.core.view.MenuItemCompat
import androidx.core.view.children
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.polzzak_android.R
import com.polzzak_android.databinding.FragmentProtectorHostBinding
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.presentation.feature.root.MainActivity

class ProtectorHostFragment() : BaseFragment<FragmentProtectorHostBinding>() {

override val layoutResId = R.layout.fragment_protector_host
private lateinit var protectorNavController: NavController

private val hostViewModel by viewModels<HostViewModel>()

override fun initView() {
super.initView()

setupNavigationView()
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, backPressedCallback)
initFragmentResultListener()
}

override fun onResume() {
super.onResume()
hostViewModel.requestHasNewNotification()
}

private fun setupNavigationView() {
Expand All @@ -36,7 +50,14 @@ class ProtectorHostFragment() : BaseFragment<FragmentProtectorHostBinding>() {
when (destination.id) {
R.id.protectorMainFragment, R.id.protectorCouponFragment, R.id.protectorNotificationFragment, R.id.protectorMyPageFragment -> {
btmNav.visibility = View.VISIBLE

if (destination.id != R.id.protectorNotificationFragment && hostViewModel.isSelectedNotificationTab) {
hostViewModel.requestHasNewNotification()
}
hostViewModel.isSelectedNotificationTab =
(destination.id == R.id.protectorNotificationFragment)
}

else -> {
btmNav.visibility = View.GONE
}
Expand All @@ -49,4 +70,33 @@ class ProtectorHostFragment() : BaseFragment<FragmentProtectorHostBinding>() {
protectorNavController.popBackStack()
}
}

override fun initObserver() {
super.initObserver()
hostViewModel.hasNewNotificationLiveData.observe(viewLifecycleOwner) {
val context = requireContext()
val notificationMenuItem =
binding.protectorBtmNav.menu.children.find { menuItem -> menuItem.itemId == R.id.protectorNotificationFragment }
notificationMenuItem?.let { menuItem ->
if (it) {
MenuItemCompat.setIconTintMode(menuItem, PorterDuff.Mode.DST)
menuItem.icon =
ContextCompat.getDrawable(context, R.drawable.selector_has_notification)
} else {
MenuItemCompat.setIconTintMode(menuItem, null)
menuItem.icon =
ContextCompat.getDrawable(context, R.drawable.ic_menu_notification)
}
}
}
}

private fun initFragmentResultListener() {
activity?.supportFragmentManager?.setFragmentResultListener(
MainActivity.NOTIFICATION_INTENT_REQUEST_KEY,
viewLifecycleOwner
) { _, _ ->
binding.protectorBtmNav.selectedItemId = R.id.protectorNotificationFragment
}
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_has_notification_selected_false.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,22C13.1,22 14,21.1 14,20H10C10,21.1 10.89,22 12,22ZM18,16V11C18,7.93 16.36,5.36 13.5,4.68V4C13.5,3.17 12.83,2.5 12,2.5C11.17,2.5 10.5,3.17 10.5,4V4.68C7.63,5.36 6,7.92 6,11V16L4,18V19H20V18L18,16Z"
android:fillColor="#EEEEF4"/>
<path
android:pathData="M21,3m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#FF6F50"/>
</vector>
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_has_notification_selected_true.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,22C13.1,22 14,21.1 14,20H10C10,21.1 10.89,22 12,22ZM18,16V11C18,7.93 16.36,5.36 13.5,4.68V4C13.5,3.17 12.83,2.5 12,2.5C11.17,2.5 10.5,3.17 10.5,4V4.68C7.63,5.36 6,7.92 6,11V16L4,18V19H20V18L18,16Z"
android:fillColor="#59B9FF"/>
<path
android:pathData="M21,3m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:fillColor="#FF6F50"/>
</vector>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/selector_has_notification.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_has_notification_selected_false" android:state_checked="false" />
<item android:drawable="@drawable/ic_has_notification_selected_true" android:state_checked="true" />
</selector>

0 comments on commit e4419b1

Please sign in to comment.