Skip to content

Commit 5df3ce7

Browse files
authored
[MERGE] #382 -> develop
[FEAT/#382] FCM ν‘Έμ‹œμ•Œλ¦Ό μ»€μŠ€ν…€ / Payload ν˜•μ‹μ— 맞게 κ΅¬ν˜„
2 parents 766350e + a08dc32 commit 5df3ce7

17 files changed

Lines changed: 220 additions & 106 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.terning.core.designsystem.type
2+
3+
enum class NotificationRedirect(val path: String) {
4+
CALENDAR("calendar"),
5+
HOME("home"),
6+
SEARCH("search");
7+
8+
companion object {
9+
fun from(type: String?): NotificationRedirect? =
10+
entries.firstOrNull { it.path.equals(type, ignoreCase = true) }
11+
}
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.terning.core.designsystem.util
2+
3+
object DeeplinkDefaults {
4+
const val REDIRECT: String = "redirect"
5+
6+
fun build(base: String) = "terning://${base}"
7+
}

β€Žcore/firebase/build.gradle.ktsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ android {
1111
dependencies {
1212
//core
1313
implementation(projects.core.navigator)
14+
implementation(projects.core.designsystem)
1415

1516
// domain
1617
implementation(projects.domain.user)
@@ -23,4 +24,7 @@ dependencies {
2324
implementation(libs.firebase.analytics)
2425
implementation(libs.firebase.messaging)
2526
implementation(libs.firebase.config)
27+
28+
// coil
29+
implementation(libs.coil.compose)
2630
}
Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package com.terning.core.firebase.messageservice
22

3+
import android.app.ActivityManager
34
import android.app.NotificationChannel
45
import android.app.NotificationManager
56
import android.app.PendingIntent
7+
import android.content.Context
68
import android.content.Intent
79
import androidx.core.app.NotificationCompat
8-
import androidx.core.app.NotificationManagerCompat
910
import androidx.core.content.getSystemService
1011
import androidx.core.net.toUri
12+
import coil3.BitmapImage
13+
import coil3.ImageLoader
14+
import coil3.request.ImageRequest
1115
import com.google.firebase.messaging.FirebaseMessagingService
1216
import com.google.firebase.messaging.RemoteMessage
17+
import com.terning.core.designsystem.type.NotificationRedirect
18+
import com.terning.core.designsystem.util.DeeplinkDefaults
19+
import com.terning.core.designsystem.util.DeeplinkDefaults.REDIRECT
1320
import com.terning.core.firebase.R
1421
import com.terning.domain.user.repository.UserRepository
1522
import com.terning.navigator.NavigatorProvider
@@ -36,44 +43,53 @@ class TerningMessagingService : FirebaseMessagingService() {
3643
override fun handleIntent(intent: Intent?) {
3744
super.handleIntent(intent)
3845

39-
if (intent?.getStringExtra(TITLE)?.isEmpty() == true
40-
|| !userRepository.getAlarmAvailable()
41-
) return
42-
43-
val title = intent?.getStringExtra(TITLE).orEmpty()
44-
val body = intent?.getStringExtra(BODY).orEmpty()
45-
val type = intent?.getStringExtra(TYPE).orEmpty()
46-
47-
sendNotification(
48-
title = title,
49-
body = body,
50-
type = type
46+
extractInformation(
47+
title = intent?.getStringExtra(TITLE),
48+
body = intent?.getStringExtra(BODY),
49+
type = intent?.getStringExtra(TYPE),
50+
imageUrl = intent?.getStringExtra(IMAGE_URL)
5151
)
5252
}
5353

5454
override fun onMessageReceived(message: RemoteMessage) {
5555
super.onMessageReceived(message)
5656

57-
if (message.data.isEmpty()
58-
|| !userRepository.getAlarmAvailable()
59-
) return
57+
extractInformation(
58+
title = message.data[TITLE],
59+
body = message.data[BODY],
60+
type = message.data[TYPE],
61+
imageUrl = message.data[IMAGE_URL]
62+
)
63+
}
6064

61-
val title = message.data[TITLE].orEmpty()
62-
val body = message.data[BODY].orEmpty()
63-
val type = message.data[TYPE].orEmpty()
65+
private fun extractInformation(
66+
title: String?,
67+
body: String?,
68+
type: String?,
69+
imageUrl: String?
70+
) {
71+
if (title.isNullOrEmpty() || !userRepository.getAlarmAvailable()) return
6472

6573
sendNotification(
6674
title = title,
67-
body = body,
68-
type = type
75+
body = body.orEmpty(),
76+
type = type.orEmpty(),
77+
imageUrl = imageUrl.orEmpty()
6978
)
7079
}
7180

72-
private fun sendNotification(title: String, body: String, type: String) {
81+
private fun sendNotification(
82+
title: String,
83+
body: String,
84+
type: String,
85+
imageUrl: String
86+
) {
7387
val notifyId = Random().nextInt()
74-
val intent = navigatorProvider.getMainActivityIntent(deeplink = type).apply {
88+
val isForeground = isAppInForeground()
89+
val deeplink = buildDeeplink(type, isForeground)
90+
val intent = navigatorProvider.getMainActivityIntent(deeplink = deeplink).apply {
7591
action = Intent.ACTION_VIEW
76-
data = type.toUri()
92+
data = deeplink.toUri()
7793
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
7894
}
7995
val pendingIntent = PendingIntent.getActivity(
@@ -82,32 +98,66 @@ class TerningMessagingService : FirebaseMessagingService() {
8298
intent,
8399
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
84100
)
85-
val channelId: String = CHANNEL_ID
86101
val notificationBuilder =
87-
NotificationCompat.Builder(this, channelId).apply {
102+
NotificationCompat.Builder(this, CHANNEL_ID).apply {
88103
setSmallIcon(R.mipmap.ic_terning_launcher)
89104
setContentTitle(title)
90105
setContentText(body)
91-
setPriority(NotificationManagerCompat.IMPORTANCE_HIGH)
106+
setAutoCancel(true)
107+
setPriority(NotificationCompat.PRIORITY_HIGH)
92108
setContentIntent(pendingIntent)
93109
}
94-
95-
getSystemService<NotificationManager>()?.run {
96-
createNotificationChannel(
97-
NotificationChannel(
98-
channelId,
99-
channelId,
100-
NotificationManager.IMPORTANCE_HIGH,
101-
),
110+
val notificationManager = getSystemService<NotificationManager>()
111+
notificationManager?.createNotificationChannel(
112+
NotificationChannel(
113+
CHANNEL_ID,
114+
CHANNEL_ID,
115+
NotificationManager.IMPORTANCE_HIGH
102116
)
103-
notify(notifyId, notificationBuilder.build())
104-
}
117+
)
118+
val imageLoader = ImageLoader(this)
119+
val request = ImageRequest.Builder(this)
120+
.data(imageUrl)
121+
.target(
122+
onSuccess = { image ->
123+
val bitmap = (image as BitmapImage).bitmap
124+
notificationBuilder.setLargeIcon(bitmap)
125+
notificationManager?.notify(notifyId, notificationBuilder.build())
126+
},
127+
onError = {
128+
notificationManager?.notify(notifyId, notificationBuilder.build())
129+
}
130+
)
131+
.build()
132+
133+
imageLoader.enqueue(request)
134+
}
135+
136+
private fun isAppInForeground(): Boolean {
137+
val appProcesses =
138+
(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses
139+
140+
return appProcesses?.any {
141+
val isForeground =
142+
it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
143+
val isCurrentApp = it.processName == packageName
144+
145+
isForeground && isCurrentApp
146+
} == true
147+
}
148+
149+
private fun buildDeeplink(type: String, isForeground: Boolean): String {
150+
val base = NotificationRedirect.from(type) ?: return ""
151+
152+
return if (isForeground) DeeplinkDefaults.build(base.path)
153+
else DeeplinkDefaults.build("splash?$REDIRECT=${base.path}")
105154
}
106155

107156
companion object {
108157
private const val CHANNEL_ID: String = "terning"
109158
private const val TITLE: String = "title"
110159
private const val BODY: String = "body"
111160
private const val TYPE: String = "type"
161+
private const val IMAGE_URL: String = "imageUrl"
112162
}
113-
}
163+
}

β€Žfeature/calendar/src/main/java/com/terning/feature/calendar/calendar/CalendarRoute.ktβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ fun CalendarRoute(
4848
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
4949
val amplitudeTracker = LocalTracker.current
5050

51-
5251
CalendarScreen(
5352
uiState = uiState,
5453
navigateToAnnouncement = navigateToAnnouncement,

β€Žfeature/calendar/src/main/java/com/terning/feature/calendar/calendar/navigation/CalendarNavigation.ktβ€Ž

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import androidx.navigation.NavController
77
import androidx.navigation.NavGraphBuilder
88
import androidx.navigation.NavOptions
99
import androidx.navigation.compose.composable
10+
import androidx.navigation.navDeepLink
11+
import com.terning.core.designsystem.util.DeeplinkDefaults
1012
import com.terning.core.navigation.MainTabRoute
1113
import com.terning.feature.calendar.calendar.CalendarRoute
1214
import kotlinx.serialization.Serializable
1315

14-
1516
fun NavController.navigateCalendar(navOptions: NavOptions? = null) {
1617
navigate(
1718
route = Calendar,
@@ -23,7 +24,13 @@ fun NavGraphBuilder.calendarNavGraph(
2324
navigateIntern: (Long) -> Unit,
2425
paddingValues: PaddingValues
2526
) {
26-
composable<Calendar> {
27+
composable<Calendar>(
28+
deepLinks = listOf(
29+
navDeepLink<Calendar>(
30+
basePath = DeeplinkDefaults.build("calendar")
31+
)
32+
)
33+
) {
2734
CalendarRoute(
2835
modifier = Modifier.padding(paddingValues),
2936
navigateToAnnouncement = navigateIntern

β€Žfeature/home/src/main/java/com/terning/feature/home/HomeRoute.ktβ€Ž

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.LazyListScope
1919
import androidx.compose.material3.Text
2020
import androidx.compose.runtime.Composable
2121
import androidx.compose.runtime.LaunchedEffect
22-
import androidx.compose.runtime.SideEffect
2322
import androidx.compose.runtime.getValue
2423
import androidx.compose.runtime.mutableStateOf
2524
import androidx.compose.runtime.remember
@@ -41,7 +40,6 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
4140
import com.google.accompanist.permissions.PermissionStatus
4241
import com.google.accompanist.permissions.isGranted
4342
import com.google.accompanist.permissions.rememberPermissionState
44-
import com.google.accompanist.systemuicontroller.rememberSystemUiController
4543
import com.terning.core.analytics.EventType
4644
import com.terning.core.analytics.LocalTracker
4745
import com.terning.core.designsystem.R.raw.paging_loading_animation
@@ -105,28 +103,16 @@ fun HomeRoute(
105103
}
106104
}
107105

108-
val systemUiController = rememberSystemUiController()
109-
SideEffect {
110-
systemUiController.setStatusBarColor(
111-
color = White
112-
)
113-
systemUiController.setNavigationBarColor(
114-
color = White
115-
)
116-
}
117-
118-
val lifecycleOwner = LocalLifecycleOwner.current
119-
val context = LocalContext.current
120-
121-
val amplitudeTracker = LocalTracker.current
122-
123106
LaunchedEffect(key1 = true) {
124107
viewModel.getProfile()
125108
viewModel.getFilteringInfo()
126109
viewModel.getHomeUpcomingInternList()
127110
viewModel.getRecommendInternFlow()
128111
}
129112

113+
val lifecycleOwner = LocalLifecycleOwner.current
114+
val context = LocalContext.current
115+
130116
LaunchedEffect(viewModel.homeSideEffect, lifecycleOwner) {
131117
viewModel.homeSideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
132118
.collect { sideEffect ->
@@ -138,6 +124,8 @@ fun HomeRoute(
138124
}
139125
}
140126

127+
val amplitudeTracker = LocalTracker.current
128+
141129
HomeScreen(
142130
paddingValues = paddingValues,
143131
navigateToIntern = {

β€Žfeature/home/src/main/java/com/terning/feature/home/navigation/HometNavigation.ktβ€Ž

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.terning.feature.home.navigation
22

3+
import android.os.Build
4+
import androidx.annotation.RequiresApi
35
import androidx.compose.foundation.layout.PaddingValues
46
import androidx.navigation.NavController
57
import androidx.navigation.NavGraphBuilder
68
import androidx.navigation.NavOptions
79
import androidx.navigation.compose.composable
10+
import androidx.navigation.navDeepLink
11+
import com.terning.core.designsystem.util.DeeplinkDefaults
812
import com.terning.core.navigation.MainTabRoute
913
import com.terning.feature.home.HomeRoute
1014
import kotlinx.serialization.Serializable
@@ -16,12 +20,19 @@ fun NavController.navigateHome(navOptions: NavOptions? = null) {
1620
)
1721
}
1822

23+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
1924
fun NavGraphBuilder.homeNavGraph(
2025
paddingValues: PaddingValues,
2126
navigateToCalendar: () -> Unit,
2227
navigateToIntern: (Long) -> Unit
2328
) {
24-
composable<Home> {
29+
composable<Home>(
30+
deepLinks = listOf(
31+
navDeepLink<Home>(
32+
basePath = DeeplinkDefaults.build("home")
33+
)
34+
)
35+
) {
2536
HomeRoute(
2637
paddingValues = paddingValues,
2738
navigateToCalendar = navigateToCalendar,

β€Žfeature/main/build.gradle.ktsβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ android {
1111
dependencies {
1212
// core
1313
implementation(projects.core.navigator)
14+
implementation(projects.core.firebase)
15+
implementation(projects.core.designsystem)
1416

1517
// feature
1618
implementation(projects.feature.calendar)

β€Žfeature/main/src/main/java/com/terning/feature/main/MainActivity.ktβ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.runtime.CompositionLocalProvider
1212
import com.terning.core.analytics.AmplitudeTracker
1313
import com.terning.core.analytics.LocalTracker
1414
import com.terning.core.designsystem.theme.TerningPointTheme
15+
import com.terning.core.designsystem.util.DeeplinkDefaults.REDIRECT
1516
import dagger.hilt.android.AndroidEntryPoint
1617
import javax.inject.Inject
1718

@@ -28,10 +29,12 @@ class MainActivity : ComponentActivity() {
2829
setContent {
2930
val navigator: MainNavigator = rememberMainNavigator()
3031
val redirect: String? = intent.data?.getQueryParameter(REDIRECT)
32+
val host: String? = intent.data?.host
3133

3234
TerningPointTheme {
3335
CompositionLocalProvider(LocalTracker provides tracker) {
3436
MainScreen(
37+
host = host,
3538
redirect = redirect,
3639
navigator = navigator
3740
)
@@ -41,8 +44,6 @@ class MainActivity : ComponentActivity() {
4144
}
4245

4346
companion object {
44-
private const val REDIRECT: String = "redirect"
45-
4647
fun getIntent(
4748
context: Context,
4849
) = Intent(context, MainActivity::class.java)

0 commit comments

Comments
Β (0)