@@ -8,8 +8,11 @@ import android.util.Log
88import androidx.core.app.NotificationCompat
99import com.google.firebase.messaging.FirebaseMessagingService
1010import com.google.firebase.messaging.RemoteMessage
11+ import com.google.gson.JsonElement
1112import dev.alllexey.itmowidgets.ItmoWidgetsApp
1213import dev.alllexey.itmowidgets.R
14+ import dev.alllexey.itmowidgets.core.model.ApiResponse
15+ import dev.alllexey.itmowidgets.core.model.QueueEntry
1316import dev.alllexey.itmowidgets.core.model.QueueEntryStatus
1417import dev.alllexey.itmowidgets.core.model.fcm.FcmJsonWrapper
1518import dev.alllexey.itmowidgets.core.model.fcm.impl.SportAutoSignLessonsPayload
@@ -18,88 +21,140 @@ import dev.alllexey.itmowidgets.core.model.fcm.impl.SportNewLessonsPayload
1821import dev.alllexey.itmowidgets.ui.main.MainActivity
1922import kotlinx.coroutines.CoroutineScope
2023import kotlinx.coroutines.Dispatchers
24+ import kotlinx.coroutines.SupervisorJob
25+ import kotlinx.coroutines.cancel
2126import kotlinx.coroutines.launch
22- import kotlinx.coroutines.runBlocking
27+ import kotlin.random.Random
2328
2429class MyFirebaseMessagingService : FirebaseMessagingService () {
2530
31+ private val serviceScope = CoroutineScope (SupervisorJob () + Dispatchers .IO )
32+
33+ private val appContainer by lazy { (applicationContext as ItmoWidgetsApp ).appContainer }
34+ private val gson by lazy { appContainer.gson }
35+ private val errorLogRepository by lazy { appContainer.errorLogRepository }
36+ private val myItmoApi by lazy { appContainer.myItmo.api() }
37+ private val widgetsApi by lazy { appContainer.itmoWidgets.api() }
38+
2639 override fun onNewToken (token : String ) {
2740 Log .d(TAG , " Refreshed token: $token " )
2841 sendTokenToServer(token)
2942 }
3043
3144 override fun onMessageReceived (remoteMessage : RemoteMessage ) {
32- val appContainer = (applicationContext as ItmoWidgetsApp ).appContainer
3345 Log .d(TAG , " From: ${remoteMessage.from} " )
3446
3547 remoteMessage.notification?.let {
3648 Log .d(TAG , " Message Notification Body: ${it.body} " )
3749 sendNotification(it.title, it.body)
3850 }
3951
40- // todo: remove later (test only)
4152 remoteMessage.data[" data" ]?.let { jsonPayload ->
42- val gson = appContainer.gson
53+ handleDataMessage(jsonPayload)
54+ }
55+ }
56+
57+ override fun onDestroy () {
58+ super .onDestroy()
59+ serviceScope.cancel()
60+ }
61+
62+ private fun handleDataMessage (jsonPayload : String ) {
63+ try {
64+ val wrapper = gson.fromJson(jsonPayload, FcmJsonWrapper ::class .java)
65+ when (wrapper.type) {
66+ SportNewLessonsPayload .TYPE -> handleNewLessons(wrapper.payload)
67+ SportFreeSignLessonsPayload .TYPE -> handleFreeSign(wrapper.payload)
68+ SportAutoSignLessonsPayload .TYPE -> handleAutoSign(wrapper.payload)
69+ else -> Log .w(TAG , " Unknown payload type: ${wrapper.type} " )
70+ }
71+ } catch (e: Exception ) {
72+ handleError(e)
73+ }
74+ }
75+
76+ private fun handleNewLessons (payloadJson : JsonElement ) {
77+ // unused
78+ // val data = gson.fromJson(payloadJson, SportNewLessonsPayload::class.java)
79+ // sendNotification("Уведомление фильтра", "${data.sportLessonIds.size} new lessons")
80+ }
81+
82+ private fun handleFreeSign (payloadJson : JsonElement ) {
83+ val data = gson.fromJson(payloadJson, SportFreeSignLessonsPayload ::class .java)
84+
85+ serviceScope.launch {
86+ processLessonSignUp(
87+ lessonIds = data.sportLessonIds,
88+ notificationTitle = " Автозапись (при освобождении)" ,
89+ fetchEntries = { widgetsApi.mySportFreeSignEntries() },
90+ markSatisfied = { id -> widgetsApi.markSportFreeSignEntrySatisfied(id) }
91+ )
92+ }
93+ }
94+
95+ private fun handleAutoSign (payloadJson : JsonElement ) {
96+ val data = gson.fromJson(payloadJson, SportFreeSignLessonsPayload ::class .java)
97+
98+ serviceScope.launch {
99+ processLessonSignUp(
100+ lessonIds = data.sportLessonIds,
101+ notificationTitle = " Автозапись (на прогнозируемое занятие)" ,
102+ fetchEntries = { widgetsApi.mySportAutoSignEntries() },
103+ markSatisfied = { id -> widgetsApi.markSportAutoSignEntrySatisfied(id) }
104+ )
105+ }
106+ }
107+
108+ private suspend fun <T : QueueEntry > processLessonSignUp (
109+ lessonIds : List <Long >,
110+ notificationTitle : String ,
111+ fetchEntries : suspend () -> ApiResponse <List <T >>,
112+ markSatisfied : suspend (Long ) -> Unit
113+ ) {
114+ val entriesResponse = try {
115+ fetchEntries()
116+ } catch (e: Exception ) {
117+ errorLogRepository.logThrowable(e, TAG )
118+ return
119+ }
120+
121+ lessonIds.forEach { lessonId ->
43122 try {
44- val wrapper = gson.fromJson(jsonPayload, FcmJsonWrapper ::class .java)
45- when (wrapper.type) {
46- SportNewLessonsPayload .TYPE -> {
47- val data = gson.fromJson(wrapper.payload, SportNewLessonsPayload ::class .java)
48- sendNotification(" SPORT NOTIF" , " ${data.sportLessonIds.size} new lessons" )
49- }
50- SportFreeSignLessonsPayload .TYPE -> {
51- val data = gson.fromJson(wrapper.payload, SportFreeSignLessonsPayload ::class .java)
52- CoroutineScope (Dispatchers .IO ).launch {
53- val freeSignEntries = appContainer.itmoWidgets.api().mySportFreeSignEntries()
54- data.sportLessonIds.forEach {
55- try {
56- appContainer.myItmo.api().signInLessons(listOf (it)).execute().body()!! .result
57- freeSignEntries.data?.firstOrNull { e -> e.status == QueueEntryStatus .NOTIFIED }
58- ?.let { e -> appContainer.itmoWidgets.api().markSportFreeSignEntrySatisfied(e.id) }
59- sendNotification(" Автозапись (при освобождении)" , " Вы успешно записаны на занятие!" )
60- } catch (e: Exception ) {
61- appContainer.errorLogRepository.logThrowable(e,
62- MyFirebaseMessagingService ::class .java.name)
63- }
64- }
65- }
66- }
67- SportAutoSignLessonsPayload .TYPE -> {
68- val data = gson.fromJson(wrapper.payload, SportFreeSignLessonsPayload ::class .java)
69- CoroutineScope (Dispatchers .IO ).launch {
70- val autoSignEntries = appContainer.itmoWidgets.api().mySportAutoSignEntries()
71- data.sportLessonIds.forEach {
72- try {
73- appContainer.myItmo.api().signInLessons(listOf (it)).execute().body()!! .result
74- autoSignEntries.data?.firstOrNull { e -> e.status == QueueEntryStatus .NOTIFIED }
75- ?.let { e -> appContainer.itmoWidgets.api().markSportAutoSignEntrySatisfied(e.id) }
76- sendNotification(" Автозапись (на прогнозируемое занятие)" , " Вы успешно записаны на занятие!" )
77- } catch (e: Exception ) {
78- appContainer.errorLogRepository.logThrowable(e,
79- MyFirebaseMessagingService ::class .java.name)
80- }
81- }
123+ val body = myItmoApi.signInLessons(listOf (lessonId)).execute().body()
124+
125+ if (body?.result != null ) {
126+ entriesResponse.data
127+ ?.firstOrNull { e -> e.status == QueueEntryStatus .NOTIFIED }
128+ ?.let { entry ->
129+ markSatisfied(entry.id)
82130 }
83- }
131+
132+ sendNotification(notificationTitle, " Вы успешно записаны на занятие!" )
133+ } else {
134+ throw RuntimeException (" Could not sign in sport lesson: ${body?.errorMessage} " )
84135 }
85136 } catch (e: Exception ) {
86- try {
87- sendNotification(" FCM" , " Ошибка обработки события, посмотрите логи" )
88- appContainer.errorLogRepository.logThrowable(e, MyFirebaseMessagingService ::class .java.name)
89- } catch (e: Exception ) {
90- appContainer.errorLogRepository.logThrowable(e, MyFirebaseMessagingService ::class .java.name)
91- }
137+ errorLogRepository.logThrowable(e, TAG )
92138 }
93139 }
94140 }
95141
96142 private fun sendNotification (title : String? , messageBody : String? ) {
97- val intent = Intent (this , MainActivity ::class .java)
98- intent.addFlags(Intent .FLAG_ACTIVITY_CLEAR_TOP )
99- val pendingIntent = PendingIntent .getActivity(this , 0 , intent,
100- PendingIntent .FLAG_ONE_SHOT or PendingIntent .FLAG_IMMUTABLE )
143+ val intent = Intent (this , MainActivity ::class .java).apply {
144+ addFlags(Intent .FLAG_ACTIVITY_CLEAR_TOP )
145+ }
146+
147+ val pendingIntent = PendingIntent .getActivity(
148+ this ,
149+ 0 ,
150+ intent,
151+ PendingIntent .FLAG_ONE_SHOT or PendingIntent .FLAG_IMMUTABLE
152+ )
101153
102154 val channelId = getString(R .string.default_notification_channel_id)
155+
156+ createNotificationChannel(channelId)
157+
103158 val notificationBuilder = NotificationCompat .Builder (this , channelId)
104159 .setSmallIcon(R .drawable.ic_notification)
105160 .setContentTitle(title)
@@ -109,22 +164,41 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
109164
110165 val notificationManager = getSystemService(NOTIFICATION_SERVICE ) as NotificationManager
111166
112- val channel = NotificationChannel (
113- channelId,
114- " Default Channel" ,
115- NotificationManager .IMPORTANCE_DEFAULT
116- )
117- notificationManager.createNotificationChannel(channel)
167+ val notificationId = (messageBody?.hashCode() ? : Random .nextInt())
168+ notificationManager.notify(notificationId, notificationBuilder.build())
169+ }
118170
119- notificationManager.notify(0 , notificationBuilder.build())
171+ private fun createNotificationChannel (channelId : String ) {
172+ val notificationManager = getSystemService(NOTIFICATION_SERVICE ) as NotificationManager
173+ if (notificationManager.getNotificationChannel(channelId) == null ) {
174+ val channel = NotificationChannel (
175+ channelId,
176+ " Default Channel" ,
177+ NotificationManager .IMPORTANCE_DEFAULT
178+ )
179+ notificationManager.createNotificationChannel(channel)
180+ }
120181 }
121182
122183 private fun sendTokenToServer (token : String ) {
123- val appContainer = (applicationContext as ItmoWidgetsApp ).appContainer
124- val itmoWidgets = appContainer.itmoWidgets
125184 appContainer.storage.utility.setFirebaseToken(token)
126185 if (appContainer.storage.settings.getCustomServicesState()) {
127- runBlocking { itmoWidgets.sendFirebaseToken(token) }
186+ serviceScope.launch {
187+ try {
188+ appContainer.itmoWidgets.sendFirebaseToken(token)
189+ } catch (e: Exception ) {
190+ Log .e(TAG , " Failed to send token" , e)
191+ }
192+ }
193+ }
194+ }
195+
196+ private fun handleError (e : Exception ) {
197+ try {
198+ sendNotification(" FCM Error" , " Ошибка обработки события, посмотрите логи" )
199+ errorLogRepository.logThrowable(e, TAG )
200+ } catch (innerEx: Exception ) {
201+ Log .e(TAG , " Error handling failed" , innerEx)
128202 }
129203 }
130204
0 commit comments