FCM 설정

FCM 토큰 업데이트 API 적용
This commit is contained in:
klaus 2023-07-25 03:20:42 +09:00
parent fd8c4e726d
commit 6f86663a54
11 changed files with 211 additions and 5 deletions

View File

@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name=".app.SodaLiveApp" android:name=".app.SodaLiveApp"
@ -38,6 +40,21 @@
<activity <activity
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity" android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight" /> android:theme="@style/Theme.AppCompat.DayNight" />
</application>
<!-- [START firebase_service] -->
<service
android:name=".fcm.SodaFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- [END firebase_service] -->
<!-- [START fcm_default_channel] -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<!-- [END fcm_default_channel] -->
</application>
</manifest> </manifest>

View File

@ -8,12 +8,14 @@ object Constants {
const val PREF_IS_ADULT = "pref_is_adult" const val PREF_IS_ADULT = "pref_is_adult"
const val PREF_NICKNAME = "pref_nickname" const val PREF_NICKNAME = "pref_nickname"
const val PREF_USER_ROLE = "pref_user_role" const val PREF_USER_ROLE = "pref_user_role"
const val PREF_PUSH_TOKEN = "pref_push_token"
const val PREF_PROFILE_IMAGE = "pref_profile_image" const val PREF_PROFILE_IMAGE = "pref_profile_image"
const val EXTRA_DATA = "extra_data" const val EXTRA_DATA = "extra_data"
const val EXTRA_TERMS = "extra_terms" const val EXTRA_TERMS = "extra_terms"
const val EXTRA_USER_ID = "extra_user_id" const val EXTRA_USER_ID = "extra_user_id"
const val EXTRA_ROOM_ID = "extra_room_id" const val EXTRA_ROOM_ID = "extra_room_id"
const val EXTRA_MESSAGE_ID = "extra_message_id"
const val EXTRA_AUDIO_CONTENT_ID = "extra_audio_content_id" const val EXTRA_CONTENT_ID = "extra_content_id"
} }

View File

@ -92,4 +92,10 @@ object SharedPreferenceManager {
set(value) { set(value) {
sharedPreferences[Constants.PREF_IS_ADULT] = value sharedPreferences[Constants.PREF_IS_ADULT] = value
} }
var pushToken: String
get() = sharedPreferences[Constants.PREF_PUSH_TOKEN, ""]
set(value) {
sharedPreferences[Constants.PREF_PUSH_TOKEN] = value
}
} }

View File

@ -0,0 +1,89 @@
package kr.co.vividnext.sodalive.fcm
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.splash.SplashActivity
class SodaFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (SharedPreferenceManager.token.isNotBlank()) {
when {
remoteMessage.data.isNotEmpty() -> {
sendNotification(remoteMessage.data)
}
}
}
}
override fun onNewToken(token: String) {
SharedPreferenceManager.pushToken = token
}
private fun sendNotification(messageData: Map<String, String>) {
val notificationChannelId = getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(
notificationChannelId,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
val intent = Intent(this, SplashActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val roomId = messageData["room_id"]
if (roomId != null) {
intent.putExtra(Constants.EXTRA_ROOM_ID, roomId.toLong())
}
val socdocId = messageData["message_id"]
if (socdocId != null) {
intent.putExtra(Constants.EXTRA_MESSAGE_ID, socdocId.toLong())
}
val audioContentId = messageData["content_id"]
if (audioContentId != null) {
intent.putExtra(Constants.EXTRA_CONTENT_ID, audioContentId.toLong())
}
val pendingIntent =
PendingIntent.getActivity(
this,
System.currentTimeMillis().toInt(),
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
.setContentTitle(messageData["title"])
.setContentText(messageData["message"])
.setSound(defaultSoundUri)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
val bigTextStyle = NotificationCompat.BigTextStyle(notificationBuilder)
bigTextStyle.bigText(messageData["message"])
notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
}
}

View File

@ -1,12 +1,19 @@
package kr.co.vividnext.sodalive.main package kr.co.vividnext.sodalive.main
import android.Manifest
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import com.google.firebase.messaging.FirebaseMessaging
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission
import com.orhanobut.logger.Logger
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.content.main.ContentMainFragment import kr.co.vividnext.sodalive.content.main.ContentMainFragment
import kr.co.vividnext.sodalive.databinding.ActivityMainBinding import kr.co.vividnext.sodalive.databinding.ActivityMainBinding
import kr.co.vividnext.sodalive.databinding.ItemMainTabBinding import kr.co.vividnext.sodalive.databinding.ItemMainTabBinding
@ -27,8 +34,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
checkPermissions()
setupBottomTabLayout() pushTokenUpdate()
getMemberInfo() getMemberInfo()
} }
@ -47,6 +54,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
isNotifiedMessage isNotifiedMessage
) )
} }
setupBottomTabLayout()
} }
private fun setupBottomTabLayout() { private fun setupBottomTabLayout() {
@ -196,6 +205,41 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
fragmentTransaction.commitNow() fragmentTransaction.commitNow()
} }
private fun checkPermissions() {
val permissions = mutableListOf(Manifest.permission.RECORD_AUDIO)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
}
TedPermission.create()
.setPermissionListener(object : PermissionListener {
override fun onPermissionGranted() {
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
}
})
.setDeniedMessage(R.string.record_audio_permission_denied_message)
.setPermissions(*permissions.toTypedArray())
.check()
}
private fun pushTokenUpdate() {
FirebaseMessaging.getInstance().token.addOnCompleteListener {
if (!it.isSuccessful) {
Logger.v("Fetching FCM registration token failed", it.exception)
return@addOnCompleteListener
}
val pushToken = it.result
if (pushToken != null) {
SharedPreferenceManager.pushToken = pushToken
viewModel.pushTokenUpdate(pushToken)
}
}
}
private fun getMemberInfo() { private fun getMemberInfo() {
viewModel.getMemberInfo { viewModel.getMemberInfo {
notificationSettingsDialog.show(screenWidth) notificationSettingsDialog.show(screenWidth)

View File

@ -60,6 +60,20 @@ class MainViewModel(
) )
} }
fun pushTokenUpdate(pushToken: String) {
val request = PushTokenUpdateRequest(
pushToken = pushToken
)
compositeDisposable.add(
userRepository
.updatePushToken(request, "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({}, {})
)
}
fun getMemberInfo(showNotificationSettingsDialog: () -> Unit) { fun getMemberInfo(showNotificationSettingsDialog: () -> Unit) {
compositeDisposable.add( compositeDisposable.add(
userRepository.getMemberInfo(token = "Bearer ${SharedPreferenceManager.token}") userRepository.getMemberInfo(token = "Bearer ${SharedPreferenceManager.token}")

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.main
import com.google.gson.annotations.SerializedName
data class PushTokenUpdateRequest(
@SerializedName("pushToken") val pushToken: String,
@SerializedName("container") val container: String = "aos"
)

View File

@ -145,7 +145,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding
) )
} else if (audioContentIdString != null) { } else if (audioContentIdString != null) {
bundleOf( bundleOf(
Constants.EXTRA_AUDIO_CONTENT_ID to audioContentIdString.toLong() Constants.EXTRA_CONTENT_ID to audioContentIdString.toLong()
) )
} else { } else {
null null

View File

@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.user
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest
import kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponse import kr.co.vividnext.sodalive.settings.notification.GetMemberInfoResponse
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest
@ -14,6 +15,7 @@ import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.Multipart import retrofit2.http.Multipart
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part import retrofit2.http.Part
interface UserApi { interface UserApi {
@ -40,4 +42,10 @@ interface UserApi {
@Body request: UpdateNotificationSettingRequest, @Body request: UpdateNotificationSettingRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<Any>>
@PUT("/member/push-token/update")
fun updatePushToken(
@Body request: PushTokenUpdateRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
} }

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.user package kr.co.vividnext.sodalive.user
import kr.co.vividnext.sodalive.main.PushTokenUpdateRequest
import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.settings.notification.UpdateNotificationSettingRequest
import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest
import kr.co.vividnext.sodalive.user.login.LoginRequest import kr.co.vividnext.sodalive.user.login.LoginRequest
@ -21,5 +22,10 @@ class UserRepository(private val userApi: UserApi) {
token: String token: String
) = userApi.updateNotificationSettings(request, authHeader = token) ) = userApi.updateNotificationSettings(request, authHeader = token)
fun updatePushToken(
request: PushTokenUpdateRequest,
token: String
) = userApi.updatePushToken(request, authHeader = token)
fun getMemberInfo(token: String) = userApi.getMemberInfo(authHeader = token) fun getMemberInfo(token: String) = userApi.getMemberInfo(authHeader = token)
} }

View File

@ -1,3 +1,15 @@
<resources> <resources>
<string name="app_name">소다라이브</string> <string name="app_name">소다라이브</string>
<string name="picker_gallery">갤러리</string>
<string name="picker_camera">카메라</string>
<string name="picker_select">선택</string>
<string name="picker_media_permissions_hint">갤러리 접근을 위해 미디어 권한을 허용해야 합니다.</string>
<string name="picker_empty_media">미디어 라이브러리가 비어있습니다.</string>
<string name="picker_allow">허용</string>
<string name="picker_select_photo">사진선택</string>
<string name="picker_no_camera">사용할 수 있는 카메라 앱이 없습니다.</string>
<string name="default_notification_channel_id">soda_fcm_default_channel</string>
<string name="record_audio_permission_denied_message"><![CDATA[권한을 거부하시면 라이브 참여를 하실 수 없습니다.\n\n[설정]->[권한]에서 권한을 허용해 주시기 바랍니다.]]></string>
<string name="read_storage_permission_denied_message"><![CDATA[권한을 거부하시면 콘텐츠를 업로드 하실 수 없습니다.\n\n[설정]->[권한]에서 권한을 허용해 주시기 바랍니다.]]></string>
<string name="retry">다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다.</string>
</resources> </resources>