FCM 설정
FCM 토큰 업데이트 API 적용
This commit is contained in:
		| @@ -3,6 +3,8 @@ | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.RECORD_AUDIO" /> | ||||
|     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | ||||
|  | ||||
|     <application | ||||
|         android:name=".app.SodaLiveApp" | ||||
| @@ -38,6 +40,21 @@ | ||||
|         <activity | ||||
|             android:name="com.google.android.gms.oss.licenses.OssLicensesActivity" | ||||
|             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> | ||||
|   | ||||
| @@ -8,12 +8,14 @@ object Constants { | ||||
|     const val PREF_IS_ADULT = "pref_is_adult" | ||||
|     const val PREF_NICKNAME = "pref_nickname" | ||||
|     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 EXTRA_DATA = "extra_data" | ||||
|     const val EXTRA_TERMS = "extra_terms" | ||||
|     const val EXTRA_USER_ID = "extra_user_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" | ||||
| } | ||||
|   | ||||
| @@ -92,4 +92,10 @@ object SharedPreferenceManager { | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_IS_ADULT] = value | ||||
|         } | ||||
|  | ||||
|     var pushToken: String | ||||
|         get() = sharedPreferences[Constants.PREF_PUSH_TOKEN, ""] | ||||
|         set(value) { | ||||
|             sharedPreferences[Constants.PREF_PUSH_TOKEN] = value | ||||
|         } | ||||
| } | ||||
|   | ||||
| @@ -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()) | ||||
|     } | ||||
| } | ||||
| @@ -1,12 +1,19 @@ | ||||
| package kr.co.vividnext.sodalive.main | ||||
|  | ||||
| import android.Manifest | ||||
| import android.content.res.ColorStateList | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import androidx.core.content.ContextCompat | ||||
| 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.base.BaseActivity | ||||
| 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.databinding.ActivityMainBinding | ||||
| import kr.co.vividnext.sodalive.databinding.ItemMainTabBinding | ||||
| @@ -27,8 +34,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         setupBottomTabLayout() | ||||
|         checkPermissions() | ||||
|         pushTokenUpdate() | ||||
|  | ||||
|         getMemberInfo() | ||||
|     } | ||||
| @@ -47,6 +54,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl | ||||
|                 isNotifiedMessage | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         setupBottomTabLayout() | ||||
|     } | ||||
|  | ||||
|     private fun setupBottomTabLayout() { | ||||
| @@ -196,6 +205,41 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl | ||||
|         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() { | ||||
|         viewModel.getMemberInfo { | ||||
|             notificationSettingsDialog.show(screenWidth) | ||||
|   | ||||
| @@ -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) { | ||||
|         compositeDisposable.add( | ||||
|             userRepository.getMemberInfo(token = "Bearer ${SharedPreferenceManager.token}") | ||||
|   | ||||
| @@ -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" | ||||
| ) | ||||
| @@ -145,7 +145,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding | ||||
|                 ) | ||||
|             } else if (audioContentIdString != null) { | ||||
|                 bundleOf( | ||||
|                     Constants.EXTRA_AUDIO_CONTENT_ID to audioContentIdString.toLong() | ||||
|                     Constants.EXTRA_CONTENT_ID to audioContentIdString.toLong() | ||||
|                 ) | ||||
|             } else { | ||||
|                 null | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.user | ||||
|  | ||||
| import io.reactivex.rxjava3.core.Single | ||||
| 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.UpdateNotificationSettingRequest | ||||
| import kr.co.vividnext.sodalive.user.find_password.ForgotPasswordRequest | ||||
| @@ -14,6 +15,7 @@ import retrofit2.http.GET | ||||
| import retrofit2.http.Header | ||||
| import retrofit2.http.Multipart | ||||
| import retrofit2.http.POST | ||||
| import retrofit2.http.PUT | ||||
| import retrofit2.http.Part | ||||
|  | ||||
| interface UserApi { | ||||
| @@ -40,4 +42,10 @@ interface UserApi { | ||||
|         @Body request: UpdateNotificationSettingRequest, | ||||
|         @Header("Authorization") authHeader: String | ||||
|     ): Single<ApiResponse<Any>> | ||||
|  | ||||
|     @PUT("/member/push-token/update") | ||||
|     fun updatePushToken( | ||||
|         @Body request: PushTokenUpdateRequest, | ||||
|         @Header("Authorization") authHeader: String | ||||
|     ): Single<ApiResponse<Any>> | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| 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.user.find_password.ForgotPasswordRequest | ||||
| import kr.co.vividnext.sodalive.user.login.LoginRequest | ||||
| @@ -21,5 +22,10 @@ class UserRepository(private val userApi: UserApi) { | ||||
|         token: String | ||||
|     ) = userApi.updateNotificationSettings(request, authHeader = token) | ||||
|  | ||||
|     fun updatePushToken( | ||||
|         request: PushTokenUpdateRequest, | ||||
|         token: String | ||||
|     ) = userApi.updatePushToken(request, authHeader = token) | ||||
|  | ||||
|     fun getMemberInfo(token: String) = userApi.getMemberInfo(authHeader = token) | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,15 @@ | ||||
| <resources> | ||||
|     <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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user