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 55cd3e09..6644ac11 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 @@ -102,6 +102,10 @@ class StudyRepository { studyService.addRegistrationId(sid, mapOf(registrationId to true)) } + suspend fun getRegistrationIdList(sid: String): Map { + return studyService.getRegistrationIdList(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 7ed3bcc8..306fdfeb 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 @@ -167,6 +167,11 @@ interface StudyService { @Body registrationId: Map ) + @GET("studies/{sid}/registrationIds.json") + suspend fun getRegistrationIdList( + @Path("sid") sid: String + ): Map + companion object { private const val BASE_URL = BuildConfig.FIREBASE_BASE_URL private val contentType = "application/json".toMediaType() diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogFragment.kt new file mode 100644 index 00000000..fcfdc4cf --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogFragment.kt @@ -0,0 +1,91 @@ +package com.sesac.developer_study_platform.ui.common + +import android.Manifest +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +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.source.local.FcmTokenRepository +import com.sesac.developer_study_platform.databinding.DialogNotificationPermissionBinding + +class NotificationPermissionDialogFragment : DialogFragment() { + + private var _binding: DialogNotificationPermissionBinding? = null + private val binding get() = _binding!! + private val viewModel by viewModels { + NotificationPermissionDialogViewModel.create(FcmTokenRepository(requireContext())) + } + private val args by navArgs() + private val requestPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + checkNotificationKey() + } else { + Toast.makeText(context, getString(R.string.all_notification_info), Toast.LENGTH_SHORT).show() + viewModel.moveToMessage(args.studyId) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = DialogNotificationPermissionBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setYesButton() + binding.btnNo.setOnClickListener { + viewModel.moveToMessage(args.studyId) + } + setNavigation() + } + + private fun setYesButton() { + binding.btnYes.setOnClickListener { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + checkNotificationKey() + } + } + } + + private fun checkNotificationKey() { + viewModel.checkNotificationKey(args.studyId) + viewModel.checkNotificationKeyEvent.observe( + viewLifecycleOwner, + EventObserver { + viewModel.moveToMessage(args.studyId) + } + ) + } + + private fun setNavigation() { + viewModel.moveToMessageEvent.observe( + viewLifecycleOwner, + EventObserver { + val action = NotificationPermissionDialogFragmentDirections.actionGlobalToMessage(it) + findNavController().navigate(action) + } + ) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogViewModel.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogViewModel.kt new file mode 100644 index 00000000..94dcf0e8 --- /dev/null +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/common/NotificationPermissionDialogViewModel.kt @@ -0,0 +1,142 @@ +package com.sesac.developer_study_platform.ui.common + +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.async +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class NotificationPermissionDialogViewModel(private val fcmTokenRepository: FcmTokenRepository) : + ViewModel() { + + private val _checkNotificationKeyEvent: MutableLiveData> = MutableLiveData() + val checkNotificationKeyEvent: LiveData> = _checkNotificationKeyEvent + + private val _moveToMessageEvent: MutableLiveData> = MutableLiveData() + val moveToMessageEvent: LiveData> = _moveToMessageEvent + + fun checkNotificationKey(sid: String) { + viewModelScope.launch { + if (getNotificationKey(sid).isNullOrEmpty()) { + createNotificationKey(sid) + } else if (isRegistrationId(sid, fcmTokenRepository.getToken().first())) { + updateStudyGroup(sid) + } + } + } + + private 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( + "NotificationPermissionDialogViewModel-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( + "NotificationPermissionDialogViewModel-addNotificationKey", + it.message ?: "error occurred." + ) + } + } + } + + private 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( + "NotificationPermissionDialogViewModel-updateStudyGroup", + it.message ?: "error occurred." + ) + } + } + } + + private suspend fun getNotificationKey(sid: String): String? { + return viewModelScope.async { + kotlin.runCatching { + studyRepository.getNotificationKey(sid) + }.onFailure { + Log.e( + "NotificationPermissionDialogViewModel-getNotificationKey", + it.message ?: "error occurred." + ) + }.getOrNull() + }.await() + } + + private fun addRegistrationId(sid: String, registrationId: String) { + viewModelScope.launch { + kotlin.runCatching { + studyRepository.addRegistrationId(sid, registrationId) + }.onSuccess { + _checkNotificationKeyEvent.value = Event(Unit) + }.onFailure { + Log.e( + "NotificationPermissionDialogViewModel-addRegistrationId", + it.message ?: "error occurred." + ) + } + } + } + + private suspend fun isRegistrationId(sid: String, registrationId: String): Boolean { + return viewModelScope.async { + kotlin.runCatching { + studyRepository.getRegistrationIdList(sid) + }.map { + it.containsKey(registrationId) + }.onFailure { + Log.e( + "NotificationPermissionDialogViewModel-isRegistrationId", + it.message ?: "error occurred." + ) + }.getOrDefault(false) + }.await() + } + + fun moveToMessage(sid: String) { + _moveToMessageEvent.value = Event(sid) + } + + companion object { + fun create(fcmTokenRepository: FcmTokenRepository) = viewModelFactory { + initializer { + NotificationPermissionDialogViewModel(fcmTokenRepository) + } + } + } +} \ No newline at end of file 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 c2c1231e..442f272c 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 @@ -7,7 +7,6 @@ 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 @@ -51,7 +50,6 @@ class JoinStudyDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setDialog() setNavigation() setYesButton() binding.btnNo.setOnClickListener { @@ -59,16 +57,6 @@ class JoinStudyDialogFragment : DialogFragment() { } } - private fun setDialog() { - val displayMetrics = resources.displayMetrics - val widthPixels = displayMetrics.widthPixels - - val params = dialog?.window?.attributes - params?.width = (widthPixels * 0.9).toInt() - dialog?.window?.attributes = params as WindowManager.LayoutParams - dialog?.window?.setBackgroundDrawableResource(R.drawable.bg_white_radius_18dp) - } - private fun setYesButton() { binding.btnYes.setOnClickListener { viewModel.addUserStudy( @@ -87,6 +75,11 @@ class JoinStudyDialogFragment : DialogFragment() { } private fun setNavigation() { + moveToMessage() + moveToNotificationPermissionDialog() + } + + private fun moveToMessage() { viewModel.moveToMessageEvent.observe( viewLifecycleOwner, EventObserver { @@ -96,6 +89,16 @@ class JoinStudyDialogFragment : DialogFragment() { ) } + private fun moveToNotificationPermissionDialog() { + viewModel.moveToNotificationPermissionDialogEvent.observe( + viewLifecycleOwner, + EventObserver { + val action = JoinStudyDialogFragmentDirections.actionGlobalToNotificationPermissionDialog(it) + findNavController().navigate(action) + } + ) + } + private fun askNotificationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { when { @@ -107,7 +110,7 @@ class JoinStudyDialogFragment : DialogFragment() { } shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> { - // TODO 권한 이유 다이얼로그 + viewModel.moveToNotificationPermissionDialog(args.study.sid) } else -> { 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 a0463f6c..fc377626 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 @@ -30,6 +30,10 @@ class JoinStudyDialogViewModel(private val fcmTokenRepository: FcmTokenRepositor private val _moveToMessageEvent: MutableLiveData> = MutableLiveData() val moveToMessageEvent: LiveData> = _moveToMessageEvent + private val _moveToNotificationPermissionDialogEvent: MutableLiveData> = MutableLiveData() + val moveToNotificationPermissionDialogEvent: LiveData> = + _moveToNotificationPermissionDialogEvent + fun addUserStudy(sid: String, study: UserStudy) { viewModelScope.launch { kotlin.runCatching { @@ -102,6 +106,10 @@ class JoinStudyDialogViewModel(private val fcmTokenRepository: FcmTokenRepositor _moveToMessageEvent.value = Event(sid) } + fun moveToNotificationPermissionDialog(sid: String) { + _moveToNotificationPermissionDialogEvent.value = Event(sid) + } + companion object { fun create(fcmTokenRepository: FcmTokenRepository) = viewModelFactory { initializer { diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/main/MainActivity.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/main/MainActivity.kt index 2bf43d0c..ccb2a0b8 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/main/MainActivity.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/main/MainActivity.kt @@ -60,6 +60,7 @@ class MainActivity : AppCompatActivity() { R.id.dest_exit_dialog -> View.GONE R.id.dest_ban_dialog -> View.GONE R.id.dest_join_study_dialog -> View.GONE + R.id.dest_notification_permission_dialog -> View.GONE else -> View.VISIBLE } } diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/message/ExitDialogFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/message/ExitDialogFragment.kt index c32c5350..4d15b20e 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/message/ExitDialogFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/message/ExitDialogFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -32,7 +31,6 @@ class ExitDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setDialog() setNavigation() binding.btnNo.setOnClickListener { dismiss() @@ -42,16 +40,6 @@ class ExitDialogFragment : DialogFragment() { } } - private fun setDialog() { - val displayMetrics = resources.displayMetrics - val widthPixels = displayMetrics.widthPixels - - val params = dialog?.window?.attributes - params?.width = (widthPixels * 0.9).toInt() - dialog?.window?.attributes = params as WindowManager.LayoutParams - dialog?.window?.setBackgroundDrawableResource(R.drawable.bg_white_radius_18dp) - } - private fun setNavigation() { viewModel.moveToHomeEvent.observe( viewLifecycleOwner, diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/mypage/LogoutDialogFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/mypage/LogoutDialogFragment.kt index bfe2e778..ea13cb6a 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/mypage/LogoutDialogFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/mypage/LogoutDialogFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -29,8 +28,6 @@ class LogoutDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setDialogSize() - dialog?.window?.setBackgroundDrawableResource(R.drawable.bg_white_radius_18dp) setNavigation() binding.btnNo.setOnClickListener { dismiss() @@ -40,15 +37,6 @@ class LogoutDialogFragment : DialogFragment() { } } - private fun setDialogSize() { - val displayMetrics = resources.displayMetrics - val widthPixels = displayMetrics.widthPixels - - val params = dialog?.window?.attributes - params?.width = (widthPixels * 0.9).toInt() - dialog?.window?.attributes = params as WindowManager.LayoutParams - } - private fun setNavigation() { viewModel.moveToLoginEvent.observe( viewLifecycleOwner, diff --git a/app/src/main/java/com/sesac/developer_study_platform/ui/profile/BanDialogFragment.kt b/app/src/main/java/com/sesac/developer_study_platform/ui/profile/BanDialogFragment.kt index c6307d0f..f34eaabd 100644 --- a/app/src/main/java/com/sesac/developer_study_platform/ui/profile/BanDialogFragment.kt +++ b/app/src/main/java/com/sesac/developer_study_platform/ui/profile/BanDialogFragment.kt @@ -4,13 +4,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController 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.databinding.DialogBanBinding class BanDialogFragment : DialogFragment() { @@ -32,7 +30,6 @@ class BanDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setDialog() setNavigation() binding.btnNo.setOnClickListener { dismiss() @@ -42,16 +39,6 @@ class BanDialogFragment : DialogFragment() { } } - private fun setDialog() { - val displayMetrics = resources.displayMetrics - val widthPixels = displayMetrics.widthPixels - - val params = dialog?.window?.attributes - params?.width = (widthPixels * 0.9).toInt() - dialog?.window?.attributes = params as WindowManager.LayoutParams - dialog?.window?.setBackgroundDrawableResource(R.drawable.bg_white_radius_18dp) - } - private fun setNavigation() { viewModel.moveToMessageEvent.observe( viewLifecycleOwner, 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 5cd040f6..c9794d9e 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 @@ -456,6 +456,11 @@ class StudyFormFragment : Fragment() { } private fun setNavigation() { + moveToMessage() + moveToNotificationPermissionDialog() + } + + private fun moveToMessage() { viewModel.moveToMessageEvent.observe( viewLifecycleOwner, EventObserver { @@ -465,6 +470,16 @@ class StudyFormFragment : Fragment() { ) } + private fun moveToNotificationPermissionDialog() { + viewModel.moveToNotificationPermissionDialogEvent.observe( + viewLifecycleOwner, + EventObserver { + val action = StudyFormFragmentDirections.actionGlobalToNotificationPermissionDialog(it) + findNavController().navigate(action) + } + ) + } + private fun askNotificationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { when { @@ -476,7 +491,7 @@ class StudyFormFragment : Fragment() { } shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> { - // TODO 권한 이유 다이얼로그 + viewModel.moveToNotificationPermissionDialog(sid) } else -> { 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 index a7659b40..55241234 100644 --- 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 @@ -23,6 +23,10 @@ class StudyFormViewModel(private val fcmTokenRepository: FcmTokenRepository) : V private val _moveToMessageEvent: MutableLiveData> = MutableLiveData() val moveToMessageEvent: LiveData> = _moveToMessageEvent + private val _moveToNotificationPermissionDialogEvent: MutableLiveData> = MutableLiveData() + val moveToNotificationPermissionDialogEvent: LiveData> = + _moveToNotificationPermissionDialogEvent + fun createNotificationKey(sid: String) { viewModelScope.launch { val token = fcmTokenRepository.getToken().first() @@ -64,6 +68,10 @@ class StudyFormViewModel(private val fcmTokenRepository: FcmTokenRepository) : V _moveToMessageEvent.value = Event(sid) } + fun moveToNotificationPermissionDialog(sid: String) { + _moveToNotificationPermissionDialogEvent.value = Event(sid) + } + companion object { fun create(fcmTokenRepository: FcmTokenRepository) = viewModelFactory { initializer { diff --git a/app/src/main/res/drawable/bg_white_radius_18dp.xml b/app/src/main/res/drawable/bg_white_radius_18dp.xml index a5e56a87..667e3ae6 100644 --- a/app/src/main/res/drawable/bg_white_radius_18dp.xml +++ b/app/src/main/res/drawable/bg_white_radius_18dp.xml @@ -1,5 +1,8 @@ - - - - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_notification_permission.xml b/app/src/main/res/layout/dialog_notification_permission.xml new file mode 100644 index 00000000..b8fb0b1f --- /dev/null +++ b/app/src/main/res/layout/dialog_notification_permission.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index a594691b..29678cb3 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -17,6 +17,10 @@ android:id="@+id/action_global_to_message" app:destination="@id/dest_message" /> + + + + + + \ 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 587cb1e9..e474087e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,6 +66,7 @@ 로그아웃 하시겠습니까? 내보내기 하시겠습니까? 채팅방을 나가시겠습니까? + 채팅 알림 기능을 제공하기 위해 알림 권한이 필요합니다.\n계속 진행하시겠습니까? 내보내기 Repository diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 9e7c34df..c8f2f5d4 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,6 +2,7 @@ + + \ No newline at end of file