From 22d5c7395adef325503e4005d534f6c9eb9582a0 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:27:15 +0900 Subject: [PATCH 01/17] =?UTF-8?q?Chore:=20oauth=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20gitignore=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/.gitignore b/app/.gitignore index 65d12b95..98858169 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,2 +1,3 @@ /build -google-services.json \ No newline at end of file +google-services.json +/src/main/assets/service-account.json \ No newline at end of file From 470f1febba1c36ec1348eaa144694ef84df0a217 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:28:36 +0900 Subject: [PATCH 02/17] =?UTF-8?q?Chore:=20fcm=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b7aa0e6..7a66deff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,6 +21,7 @@ android { versionCode = 1 versionName = "1.0" buildConfigField("String", "FIREBASE_BASE_URL", getProperty("FIREBASE_BASE_URL")) + buildConfigField("String", "FIREBASE_SENDER_ID", getProperty("FIREBASE_SENDER_ID")) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -46,6 +47,9 @@ android { buildConfig = true dataBinding = true } + packaging { + resources.excludes.add("META-INF/*") + } } fun getProperty(key: String): String { @@ -62,6 +66,9 @@ dependencies { implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-auth") implementation("com.google.firebase:firebase-storage") + implementation("com.google.firebase:firebase-messaging") + implementation("com.google.firebase:firebase-messaging-directboot") + implementation("com.google.auth:google-auth-library-oauth2-http:1.23.0") implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.navigation:navigation-fragment-ktx:2.7.6") implementation("androidx.navigation:navigation-ui-ktx:2.7.6") From 3ffaade4133c8bb91a55ff8fa80d1d5997f38614 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:34:43 +0900 Subject: [PATCH 03/17] =?UTF-8?q?Feat:=20fcm=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20datastore=EC=97=90=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/source/local/FcmTokenRepository.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/data/source/local/FcmTokenRepository.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/local/FcmTokenRepository.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/local/FcmTokenRepository.kt new file mode 100644 index 00000000..d500fd73 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/local/FcmTokenRepository.kt @@ -0,0 +1,34 @@ +package com.sesac.developer_study_platform.data.source.local + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private const val FCM_TOKEN_DATASTORE = "fcm_token_datastore" + +val Context.fcmTokenDataStore: DataStore by preferencesDataStore(FCM_TOKEN_DATASTORE) + +class FcmTokenRepository(private val context: Context) { + + suspend fun setToken(token: String) { + context.fcmTokenDataStore.edit { preferences -> + preferences[FCM_TOKEN_KEY] = token + } + } + + fun getToken(): Flow { + return context.fcmTokenDataStore.data + .map { preferences -> + preferences[FCM_TOKEN_KEY] ?: "" + } + } + + companion object { + private val FCM_TOKEN_KEY = stringPreferencesKey("fcm_token") + } +} \ No newline at end of file From d8b00cf1d4ecc74afc070993d4491a67770f59b8 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:37:38 +0900 Subject: [PATCH 04/17] =?UTF-8?q?Feat:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=9D=B4=20=EC=83=9D=EC=84=B1=EB=90=A0=20?= =?UTF-8?q?=EB=95=8C=EB=A7=88=EB=8B=A4=20datastore=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyFirebaseMessagingService.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/MyFirebaseMessagingService.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/MyFirebaseMessagingService.kt b/app/src/main/java/com/sesac/developer_study_platform/MyFirebaseMessagingService.kt new file mode 100644 index 00000000..3db43455 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/MyFirebaseMessagingService.kt @@ -0,0 +1,38 @@ +package com.sesac.developer_study_platform + +import android.util.Log +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class MyFirebaseMessagingService : FirebaseMessagingService() { + + private val fcmTokenRepository = FcmTokenRepository(this) + + override fun onNewToken(token: String) { + super.onNewToken(token) + + CoroutineScope(Dispatchers.IO).launch { + fcmTokenRepository.setToken(token) + } + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + // TODO(developer): Handle FCM messages here. + // Not getting messages here? See why this may be: https://goo.gl/39bRNJ + Log.d("fcm", "From: ${remoteMessage.from}") + + // Check if message contains a data payload. + if (remoteMessage.data.isNotEmpty()) { + Log.d("fcm", "Message data payload: ${remoteMessage.data}") + } + + // Check if message contains a notification payload. + remoteMessage.notification?.let { + Log.d("fcm", "Message Notification Body: ${it.body}") + } + } +} \ No newline at end of file From dac1376388c52c3580882c3967aaaf9125c5cc8e Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:39:53 +0900 Subject: [PATCH 05/17] =?UTF-8?q?Chore:=20manifest=EC=97=90=20FirebaseMess?= =?UTF-8?q?agingService=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 15 +++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 17 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8f48849b..4a3e847a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,5 +26,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1502887c..71251b4e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,8 @@ com.sesac.developer_study_platform.DEVELOPER_STUDY_APP + fcm_default_channel + 검색 방만들기 From 8b34791c7ffb8ef941442d37c6b70fcfadafa256 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:43:52 +0900 Subject: [PATCH 06/17] =?UTF-8?q?Feat:=20StudyGroup=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../developer_study_platform/data/StudyGroup.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/data/StudyGroup.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/StudyGroup.kt b/app/src/main/java/com/sesac/developer_study_platform/data/StudyGroup.kt new file mode 100644 index 00000000..9612b8bd --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/data/StudyGroup.kt @@ -0,0 +1,12 @@ +package com.sesac.developer_study_platform.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class StudyGroup( + val operation: String = "", + @SerialName("notification_key_name") val notificationKeyName: String = "", + @SerialName("registration_ids") val registrationIdList: List = listOf(), + @SerialName("notification_key") val notificationKey: String = "", +) \ No newline at end of file From 1c620ec81b0f61072783b96622a56533533c8406 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:44:22 +0900 Subject: [PATCH 07/17] =?UTF-8?q?Feat:=20FcmMessage=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../developer_study_platform/data/FcmMessage.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/data/FcmMessage.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/FcmMessage.kt b/app/src/main/java/com/sesac/developer_study_platform/data/FcmMessage.kt new file mode 100644 index 00000000..896cb275 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/data/FcmMessage.kt @@ -0,0 +1,14 @@ +package com.sesac.developer_study_platform.data + +import kotlinx.serialization.Serializable + +@Serializable +data class FcmMessage( + val message: FcmMessageData, +) + +@Serializable +data class FcmMessageData( + val token: String = "", + val data: Map = mapOf(), +) \ No newline at end of file From 41bf8cc46d849d89ae78e09c87b7040f8ea6e860 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:45:11 +0900 Subject: [PATCH 08/17] =?UTF-8?q?Feat:=20FcmService=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/source/remote/FcmService.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmService.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmService.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmService.kt new file mode 100644 index 00000000..86943033 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmService.kt @@ -0,0 +1,63 @@ +package com.sesac.developer_study_platform.data.source.remote + +import android.content.Context +import com.google.auth.oauth2.GoogleCredentials +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.sesac.developer_study_platform.BuildConfig +import com.sesac.developer_study_platform.data.FcmMessage +import com.sesac.developer_study_platform.data.StudyGroup +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.http.Body +import retrofit2.http.POST + +interface FcmService { + + @POST("fcm/notification") + suspend fun updateStudyGroup( + @Body studyGroup: StudyGroup + ): Map + + @POST("v1/projects/developer-study-platform/messages:send") + suspend fun sendNotification( + @Body message: FcmMessage + ) + + companion object { + private const val BASE_URL = "https://fcm.googleapis.com" + private const val SCOPES = "https://www.googleapis.com/auth/firebase.messaging" + private val contentType = "application/json".toMediaType() + private val jsonConfig = Json { ignoreUnknownKeys = true } + + private fun getClient(context: Context): OkHttpClient { + return OkHttpClient.Builder().addInterceptor { chain -> + val builder = chain.request().newBuilder().apply { + addHeader("access_token_auth", "true") + addHeader("Authorization", "Bearer ${getAccessToken(context)}") + addHeader("project_id", BuildConfig.FIREBASE_SENDER_ID) + } + chain.proceed(builder.build()) + }.build() + } + + private fun getAccessToken(context: Context): String { + val inputStream = context.resources.assets.open("service-account.json") + val googleCredential = GoogleCredentials + .fromStream(inputStream) + .createScoped(listOf(SCOPES)) + googleCredential.refresh() + return googleCredential.accessToken.tokenValue + } + + fun create(context: Context): FcmService { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(getClient(context)) + .addConverterFactory(jsonConfig.asConverterFactory(contentType)) + .build() + .create(FcmService::class.java) + } + } +} \ No newline at end of file From 89e40cdca33b053921437fbaedb0b97638c10a76 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:46:51 +0900 Subject: [PATCH 09/17] =?UTF-8?q?Feat:=20Fcm=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/source/remote/FcmRepository.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmRepository.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmRepository.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmRepository.kt new file mode 100644 index 00000000..2f3df458 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/FcmRepository.kt @@ -0,0 +1,18 @@ +package com.sesac.developer_study_platform.data.source.remote + +import android.content.Context +import com.sesac.developer_study_platform.data.FcmMessage +import com.sesac.developer_study_platform.data.StudyGroup + +class FcmRepository(context: Context) { + + private val fcmService = FcmService.create(context) + + suspend fun updateStudyGroup(studyGroup: StudyGroup): Map { + return fcmService.updateStudyGroup(studyGroup) + } + + suspend fun sendNotification(message: FcmMessage) { + fcmService.sendNotification(message) + } +} \ No newline at end of file From 887731eb1b97364334ef523a28ff2eb787b27ef3 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:47:37 +0900 Subject: [PATCH 10/17] =?UTF-8?q?Feat:=20fcm=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20application=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sesac/developer_study_platform/StudyApplication.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/sesac/developer_study_platform/StudyApplication.kt b/app/src/main/java/com/sesac/developer_study_platform/StudyApplication.kt index dd1423e1..e530db6c 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/StudyApplication.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/StudyApplication.kt @@ -7,6 +7,7 @@ import com.sesac.developer_study_platform.data.source.local.BookmarkDao import com.sesac.developer_study_platform.data.source.local.BookmarkRepository import com.sesac.developer_study_platform.data.source.local.MyStudyDao import com.sesac.developer_study_platform.data.source.local.MyStudyRepository +import com.sesac.developer_study_platform.data.source.remote.FcmRepository import com.sesac.developer_study_platform.data.source.remote.GithubRepository import com.sesac.developer_study_platform.data.source.remote.StudyRepository @@ -25,6 +26,7 @@ class StudyApplication : Application() { bookmarkRepository = BookmarkRepository() myStudyDao = db.myStudyDao() myStudyRepository = MyStudyRepository() + fcmRepository = FcmRepository(this) } override fun onTerminate() { @@ -39,5 +41,6 @@ class StudyApplication : Application() { lateinit var bookmarkRepository: BookmarkRepository lateinit var myStudyDao: MyStudyDao lateinit var myStudyRepository: MyStudyRepository + lateinit var fcmRepository: FcmRepository } } \ No newline at end of file From 151c11974ea27682c7602dc7fac02918bb621bef Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Wed, 6 Mar 2024 19:49:10 +0900 Subject: [PATCH 11/17] =?UTF-8?q?Feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=B3=84=EB=A1=9C=20notification=20key=20=ED=8C=8C=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EB=B2=A0=EC=9D=B4=EC=8A=A4=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=B0=8F=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/source/remote/StudyRepository.kt | 8 ++++++++ .../data/source/remote/StudyService.kt | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt index 59eb89b1..d1ef9e86 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt @@ -90,6 +90,14 @@ class StudyRepository { studyService.deleteUserStudy(uid, sid) } + suspend fun addNotificationKey(sid: String, notificationKey: String) { + studyService.addNotificationKey(sid, notificationKey) + } + + suspend fun getNotificationKey(sid: String): String { + return studyService.getNotificationKey(sid) + } + fun getMessageList(sid: String): Flow> = flow { while (true) { kotlin.runCatching { diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt index 21d3f3f4..4bb42d62 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt @@ -150,6 +150,17 @@ interface StudyService { @Path("sid") sid: String ) + @PUT("studies/{sid}/notificationKey.json") + suspend fun addNotificationKey( + @Path("sid") sid: String, + @Body notificationKey: String + ) + + @GET("studies/{sid}/notificationKey.json") + suspend fun getNotificationKey( + @Path("sid") sid: String, + ): String + companion object { private const val BASE_URL = BuildConfig.FIREBASE_BASE_URL private val contentType = "application/json".toMediaType() From 83fa94fd749b21e3602815783c8517504720131b Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:04:34 +0900 Subject: [PATCH 12/17] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=EC=97=90=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/source/remote/StudyRepository.kt | 6 +++++- .../data/source/remote/StudyService.kt | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt index d1ef9e86..55cd3e09 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyRepository.kt @@ -94,10 +94,14 @@ class StudyRepository { studyService.addNotificationKey(sid, notificationKey) } - suspend fun getNotificationKey(sid: String): String { + suspend fun getNotificationKey(sid: String): String? { return studyService.getNotificationKey(sid) } + suspend fun addRegistrationId(sid: String, registrationId: String) { + studyService.addRegistrationId(sid, mapOf(registrationId to true)) + } + fun getMessageList(sid: String): Flow> = flow { while (true) { kotlin.runCatching { diff --git a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt index 4bb42d62..7ed3bcc8 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/data/source/remote/StudyService.kt @@ -159,7 +159,13 @@ interface StudyService { @GET("studies/{sid}/notificationKey.json") suspend fun getNotificationKey( @Path("sid") sid: String, - ): String + ): String? + + @PATCH("studies/{sid}/registrationIds.json") + suspend fun addRegistrationId( + @Path("sid") sid: String, + @Body registrationId: Map + ) companion object { private const val BASE_URL = BuildConfig.FIREBASE_BASE_URL From 47bdac14853587bdc56c998af944de08fde4b073 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:07:29 +0900 Subject: [PATCH 13/17] =?UTF-8?q?Feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20notification=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/studyform/StudyFormViewModel.kt | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormViewModel.kt diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormViewModel.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormViewModel.kt new file mode 100644 index 00000000..a7659b40 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormViewModel.kt @@ -0,0 +1,74 @@ +package com.sesac.developer_study_platform.ui.studyform + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.sesac.developer_study_platform.Event +import com.sesac.developer_study_platform.StudyApplication.Companion.fcmRepository +import com.sesac.developer_study_platform.StudyApplication.Companion.studyRepository +import com.sesac.developer_study_platform.data.StudyGroup +import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class StudyFormViewModel(private val fcmTokenRepository: FcmTokenRepository) : ViewModel() { + + private val _createNotificationKeyEvent: MutableLiveData> = MutableLiveData() + val createNotificationKeyEvent: LiveData> = _createNotificationKeyEvent + + private val _moveToMessageEvent: MutableLiveData> = MutableLiveData() + val moveToMessageEvent: LiveData> = _moveToMessageEvent + + fun createNotificationKey(sid: String) { + viewModelScope.launch { + val token = fcmTokenRepository.getToken().first() + kotlin.runCatching { + fcmRepository.updateStudyGroup(StudyGroup("create", sid, listOf(token))) + }.onSuccess { + addNotificationKey(sid, it.values.first()) + }.onFailure { + Log.e("StudyFormViewModel-createNotificationKey", it.message ?: "error occurred.") + } + } + } + + private fun addNotificationKey(sid: String, notificationKey: String) { + viewModelScope.launch { + kotlin.runCatching { + studyRepository.addNotificationKey(sid, notificationKey) + }.onSuccess { + addRegistrationId(sid, fcmTokenRepository.getToken().first()) + }.onFailure { + Log.e("StudyFormViewModel-addNotificationKey", it.message ?: "error occurred.") + } + } + } + + private fun addRegistrationId(sid: String, registrationId: String) { + viewModelScope.launch { + kotlin.runCatching { + studyRepository.addRegistrationId(sid, registrationId) + }.onSuccess { + _createNotificationKeyEvent.value = Event(Unit) + }.onFailure { + Log.e("StudyFormViewModel-addRegistrationId", it.message ?: "error occurred.") + } + } + } + + fun moveToMessage(sid: String) { + _moveToMessageEvent.value = Event(sid) + } + + companion object { + fun create(fcmTokenRepository: FcmTokenRepository) = viewModelFactory { + initializer { + StudyFormViewModel(fcmTokenRepository) + } + } + } +} \ No newline at end of file From 4684189d66705a78e5976385346e75ba7c899d92 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:09:15 +0900 Subject: [PATCH 14/17] =?UTF-8?q?Feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B6=8C=ED=95=9C=20=EC=9A=94=EC=B2=AD=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/studyform/StudyFormFragment.kt | 72 +++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormFragment.kt index 166ebccd..5cd040f6 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/studyform/StudyFormFragment.kt @@ -1,18 +1,24 @@ package com.sesac.developer_study_platform.ui.studyform +import android.Manifest +import android.content.pm.PackageManager import android.net.Uri +import android.os.Build import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter +import android.widget.Toast import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.AppCompatButton +import androidx.core.content.ContextCompat import androidx.core.util.Pair import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.datepicker.CalendarConstraints @@ -24,16 +30,18 @@ import com.google.android.material.timepicker.TimeFormat import com.google.firebase.auth.ktx.auth import com.google.firebase.ktx.Firebase import com.google.firebase.storage.ktx.storage +import com.sesac.developer_study_platform.EventObserver import com.sesac.developer_study_platform.R import com.sesac.developer_study_platform.data.ChatRoom import com.sesac.developer_study_platform.data.DayTime import com.sesac.developer_study_platform.data.Study import com.sesac.developer_study_platform.data.UserStudy +import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository import com.sesac.developer_study_platform.data.source.remote.StudyService import com.sesac.developer_study_platform.databinding.FragmentStudyFormBinding -import com.sesac.developer_study_platform.util.isNetworkConnected import com.sesac.developer_study_platform.util.DateFormats import com.sesac.developer_study_platform.util.formatTimestamp +import com.sesac.developer_study_platform.util.isNetworkConnected import com.sesac.developer_study_platform.util.setImage import com.sesac.developer_study_platform.util.showSnackbar import kotlinx.coroutines.launch @@ -51,8 +59,12 @@ class StudyFormFragment : Fragment() { private var category = "" private var startDate = "" private var endDate = "" + private var sid = "" private lateinit var image: Uri private val studyService = StudyService.create() + private val viewModel by viewModels { + StudyFormViewModel.create(FcmTokenRepository(requireContext())) + } private val dayTimeAdapter = DayTimeAdapter(object : DayTimeClickListener { override fun onClick(isStartTime: Boolean, dayTime: DayTime) { showTimePicker(isStartTime, dayTime) @@ -62,6 +74,15 @@ class StudyFormFragment : Fragment() { registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> setSelectedImage(uri) } + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + createNotificationKey() + } else { + Toast.makeText(context, getString(R.string.all_notification_info), Toast.LENGTH_SHORT).show() + viewModel.moveToMessage(sid) + } + } override fun onCreateView( inflater: LayoutInflater, @@ -106,6 +127,7 @@ class StudyFormFragment : Fragment() { setTotalPeopleCount() setValidateAll() binding.isNetworkConnected = isNetworkConnected(requireContext()) + setNavigation() } private fun setImageButton() { @@ -336,7 +358,7 @@ class StudyFormFragment : Fragment() { else -> { val uid = Firebase.auth.uid uid?.let { - val sid = "@make@$uid@time@${formatTimestamp()}" + sid = "@make@$uid@time@${formatTimestamp()}" uploadImage(sid, image) { fileName -> saveStudy(sid, formatStudy(sid, uid, fileName)) saveUserStudy(uid, sid, formatUserStudy(sid, fileName)) @@ -389,8 +411,7 @@ class StudyFormFragment : Fragment() { kotlin.runCatching { studyService.addChatRoom(sid, ChatRoom()) }.onSuccess { - val action = StudyFormFragmentDirections.actionStudyFormToMessage(sid) - findNavController().navigate(action) + askNotificationPermission() }.onFailure { Log.e("StudyFormFragment-saveChatRoom", it.message ?: "error occurred.") } @@ -434,6 +455,49 @@ class StudyFormFragment : Fragment() { return list } + private fun setNavigation() { + viewModel.moveToMessageEvent.observe( + viewLifecycleOwner, + EventObserver { + val action = StudyFormFragmentDirections.actionStudyFormToMessage(it) + findNavController().navigate(action) + } + ) + } + + private fun askNotificationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + when { + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED -> { + createNotificationKey() + } + + shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> { + // TODO 권한 이유 다이얼로그 + } + + else -> { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + } else { + createNotificationKey() + } + } + + private fun createNotificationKey() { + viewModel.createNotificationKey(sid) + viewModel.createNotificationKeyEvent.observe( + viewLifecycleOwner, + EventObserver { + viewModel.moveToMessage(sid) + } + ) + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 71251b4e..587cb1e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ 스터디 검색 사람 모양의 아이콘 + 이 앱은 알림을 표시하지 않습니다. 설정에서 알림 권한을 변경할 수 있습니다. AUTO_LOGIN Login with Github From 71b82a29f034b88178b65c6406b1d7a6891fc36f Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:11:09 +0900 Subject: [PATCH 15/17] =?UTF-8?q?Feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20notification=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/detail/JoinStudyDialogViewModel.kt | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogViewModel.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogViewModel.kt index e1171ffd..a0463f6c 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogViewModel.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogViewModel.kt @@ -5,18 +5,28 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.google.firebase.Firebase import com.google.firebase.auth.auth import com.sesac.developer_study_platform.Event +import com.sesac.developer_study_platform.StudyApplication.Companion.fcmRepository import com.sesac.developer_study_platform.StudyApplication.Companion.studyRepository +import com.sesac.developer_study_platform.data.StudyGroup import com.sesac.developer_study_platform.data.UserStudy +import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -class JoinStudyDialogViewModel : ViewModel() { +class JoinStudyDialogViewModel(private val fcmTokenRepository: FcmTokenRepository) : ViewModel() { private val _addUserStudyEvent: MutableLiveData> = MutableLiveData() val addUserStudyEvent: LiveData> = _addUserStudyEvent + private val _updateStudyGroupEvent: MutableLiveData> = MutableLiveData() + val updateStudyGroupEvent: LiveData> = _updateStudyGroupEvent + private val _moveToMessageEvent: MutableLiveData> = MutableLiveData() val moveToMessageEvent: LiveData> = _moveToMessageEvent @@ -50,7 +60,53 @@ class JoinStudyDialogViewModel : ViewModel() { } } + fun updateStudyGroup(sid: String) { + viewModelScope.launch { + val token = fcmTokenRepository.getToken().first() + kotlin.runCatching { + val notificationKey = getNotificationKey(sid) + if (!notificationKey.isNullOrEmpty()) { + fcmRepository.updateStudyGroup(StudyGroup("add", sid, listOf(token), notificationKey)) + } + }.onSuccess { + addRegistrationId(sid, token) + }.onFailure { + Log.e("JoinStudyDialogViewModel-updateStudyGroup", it.message ?: "error occurred.") + } + } + } + + private suspend fun getNotificationKey(sid: String): String? { + return viewModelScope.async { + kotlin.runCatching { + studyRepository.getNotificationKey(sid) + }.onFailure { + Log.e("JoinStudyDialogViewModel-getNotificationKey", it.message ?: "error occurred.") + }.getOrNull() + }.await() + } + + private fun addRegistrationId(sid: String, registrationId: String) { + viewModelScope.launch { + kotlin.runCatching { + studyRepository.addRegistrationId(sid, registrationId) + }.onSuccess { + _updateStudyGroupEvent.value = Event(Unit) + }.onFailure { + Log.e("JoinStudyDialogViewModel-addRegistrationId", it.message ?: "error occurred.") + } + } + } + fun moveToMessage(sid: String) { _moveToMessageEvent.value = Event(sid) } + + companion object { + fun create(fcmTokenRepository: FcmTokenRepository) = viewModelFactory { + initializer { + JoinStudyDialogViewModel(fcmTokenRepository) + } + } + } } \ No newline at end of file From c8b8db4dc488b0e331136fc8c5832a917fa0d7fb Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:12:47 +0900 Subject: [PATCH 16/17] =?UTF-8?q?Feat:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=ED=99=94=EB=A9=B4=20=EC=95=8C=EB=A6=BC=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=9A=94=EC=B2=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/detail/JoinStudyDialogFragment.kt | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogFragment.kt index 32aa284b..c2c1231e 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/detail/JoinStudyDialogFragment.kt @@ -1,10 +1,16 @@ package com.sesac.developer_study_platform.ui.detail +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -12,14 +18,26 @@ import androidx.navigation.fragment.navArgs import com.sesac.developer_study_platform.EventObserver import com.sesac.developer_study_platform.R import com.sesac.developer_study_platform.data.UserStudy +import com.sesac.developer_study_platform.data.source.local.FcmTokenRepository import com.sesac.developer_study_platform.databinding.DialogJoinStudyBinding class JoinStudyDialogFragment : DialogFragment() { private var _binding: DialogJoinStudyBinding? = null private val binding get() = _binding!! - private val viewModel by viewModels() + private val viewModel by viewModels { + JoinStudyDialogViewModel.create(FcmTokenRepository(requireContext())) + } private val args by navArgs() + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + updateStudyGroup() + } else { + Toast.makeText(context, getString(R.string.all_notification_info), Toast.LENGTH_SHORT).show() + viewModel.moveToMessage(args.study.sid) + } + } override fun onCreateView( inflater: LayoutInflater, @@ -62,7 +80,7 @@ class JoinStudyDialogFragment : DialogFragment() { viewModel.addUserStudyEvent.observe( viewLifecycleOwner, EventObserver { - viewModel.moveToMessage(args.study.sid) + askNotificationPermission() } ) } @@ -78,6 +96,39 @@ class JoinStudyDialogFragment : DialogFragment() { ) } + private fun askNotificationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + when { + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED -> { + updateStudyGroup() + } + + shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> { + // TODO 권한 이유 다이얼로그 + } + + else -> { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + } else { + updateStudyGroup() + } + } + + private fun updateStudyGroup() { + viewModel.updateStudyGroup(args.study.sid) + viewModel.updateStudyGroupEvent.observe( + viewLifecycleOwner, + EventObserver { + viewModel.moveToMessage(args.study.sid) + } + ) + } + override fun onDestroyView() { super.onDestroyView() _binding = null From 77e7625c0f6023ff332243a0ad0e66e4949d1cb2 Mon Sep 17 00:00:00 2001 From: YuGyeong98 Date: Fri, 8 Mar 2024 00:27:56 +0900 Subject: [PATCH 17/17] =?UTF-8?q?Feat:=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/message/MessageFragment.kt | 1 - .../ui/message/MessageViewModel.kt | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageFragment.kt index a06eb4fa..f93557cb 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageFragment.kt @@ -74,7 +74,6 @@ class MessageFragment : Fragment() { binding.isNetworkConnected = isNetworkConnected(requireContext()) } - private fun setBackButton() { binding.toolbar.setNavigationOnClickListener { viewModel.moveToBack() diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageViewModel.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageViewModel.kt index 314c7da4..58cff01d 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageViewModel.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/message/MessageViewModel.kt @@ -10,7 +10,10 @@ import com.google.firebase.Firebase import com.google.firebase.auth.auth import com.google.firebase.storage.storage import com.sesac.developer_study_platform.Event +import com.sesac.developer_study_platform.StudyApplication.Companion.fcmRepository import com.sesac.developer_study_platform.StudyApplication.Companion.studyRepository +import com.sesac.developer_study_platform.data.FcmMessage +import com.sesac.developer_study_platform.data.FcmMessageData import com.sesac.developer_study_platform.data.Message import com.sesac.developer_study_platform.data.StudyUser import kotlinx.coroutines.SupervisorJob @@ -128,6 +131,7 @@ class MessageViewModel : ViewModel() { }.onSuccess { _addMessageEvent.value = Event(Unit) updateLastMessage(sid, message) + sendNotification(sid, message.message) }.onFailure { Log.e("MessageViewModel-sendImage", it.message ?: "error occurred.") } @@ -142,6 +146,7 @@ class MessageViewModel : ViewModel() { }.onSuccess { _addMessageEvent.value = Event(Unit) updateLastMessage(sid, message) + sendNotification(sid, message.message) }.onFailure { Log.e("MessageViewModel-sendMessage", it.message ?: "error occurred.") } @@ -256,6 +261,33 @@ class MessageViewModel : ViewModel() { } } + private fun sendNotification(sid: String, message: String) { + viewModelScope.launch { + kotlin.runCatching { + val notificationKey = getNotificationKey(sid) + if (!notificationKey.isNullOrEmpty()) { + fcmRepository.sendNotification( + FcmMessage( + FcmMessageData(notificationKey, mapOf("message" to message)) + ) + ) + } + }.onFailure { + Log.e("MessageViewModel-sendNotification", it.message ?: "error occurred.") + } + } + } + + private suspend fun getNotificationKey(sid: String): String? { + return viewModelScope.async { + kotlin.runCatching { + studyRepository.getNotificationKey(sid) + }.onFailure { + Log.e("MessageViewModel-getNotificationKey", it.message ?: "error occurred.") + }.getOrNull() + }.await() + } + fun moveToBack() { _moveToBackEvent.value = Event(Unit) }