diff --git a/core/data/src/main/java/com/teamwable/data/repository/ProfileRepository.kt b/core/data/src/main/java/com/teamwable/data/repository/ProfileRepository.kt index 0b199ea0..21f4d480 100644 --- a/core/data/src/main/java/com/teamwable/data/repository/ProfileRepository.kt +++ b/core/data/src/main/java/com/teamwable/data/repository/ProfileRepository.kt @@ -2,9 +2,12 @@ package com.teamwable.data.repository import com.teamwable.model.Profile import com.teamwable.model.profile.MemberDataModel +import com.teamwable.model.profile.MemberInfoEditModel +import java.io.File interface ProfileRepository { suspend fun getProfileInfo(userId: Long): Result suspend fun getMemberData(): Result suspend fun patchWithdrawal(deletedReason: List): Result + suspend fun patchProfileUriEdit(info: MemberInfoEditModel, file: String?): Result } diff --git a/core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultProfileRepository.kt b/core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultProfileRepository.kt index d67eefc2..33e1a630 100644 --- a/core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultProfileRepository.kt +++ b/core/data/src/main/java/com/teamwable/data/repositoryimpl/DefaultProfileRepository.kt @@ -1,16 +1,24 @@ package com.teamwable.data.repositoryimpl +import android.content.ContentResolver import com.teamwable.data.mapper.toModel.toMemberDataModel import com.teamwable.data.mapper.toModel.toProfile import com.teamwable.data.repository.ProfileRepository import com.teamwable.model.Profile import com.teamwable.model.profile.MemberDataModel +import com.teamwable.model.profile.MemberInfoEditModel import com.teamwable.network.datasource.ProfileService import com.teamwable.network.dto.request.RequestWithdrawalDto +import com.teamwable.network.util.createImagePart import com.teamwable.network.util.handleThrowable +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject import javax.inject.Inject class DefaultProfileRepository @Inject constructor( + private val contentResolver: ContentResolver, private val apiService: ProfileService, ) : ProfileRepository { override suspend fun getProfileInfo(userId: Long): Result = runCatching { @@ -31,4 +39,27 @@ class DefaultProfileRepository @Inject constructor( Unit }.onFailure { return it.handleThrowable() } } + + override suspend fun patchProfileUriEdit(info: MemberInfoEditModel, file: String?): Result { + return runCatching { + val infoRequestBody = createContentRequestBody(info) + val filePart = contentResolver.createImagePart(file) + + apiService.patchUserProfile(infoRequestBody, filePart).success + } + } + + private fun createContentRequestBody(info: MemberInfoEditModel): RequestBody { + val contentJson = JSONObject().apply { + put("nickname", info.nickname) + put("isAlarmAllowed", info.isAlarmAllowed) + put("memberIntro", info.memberIntro) + put("isPushAlarmAllowed", info.isPushAlarmAllowed) + put("fcmToken", info.fcmToken) + put("memberLckYears", info.memberLckYears) + put("memberFanTeam", info.memberFanTeam) + put("memberDefaultProfileImage", info.memberDefaultProfileImage) + }.toString() + return contentJson.toRequestBody("application/json".toMediaTypeOrNull()) + } } diff --git a/core/model/src/main/java/com/teamwable/model/profile/MemberInfoEditModel.kt b/core/model/src/main/java/com/teamwable/model/profile/MemberInfoEditModel.kt new file mode 100644 index 00000000..2b136641 --- /dev/null +++ b/core/model/src/main/java/com/teamwable/model/profile/MemberInfoEditModel.kt @@ -0,0 +1,12 @@ +package com.teamwable.model.profile + +data class MemberInfoEditModel( + val nickname: String? = "", + val isAlarmAllowed: Boolean? = false, + val memberIntro: String? = "", + val isPushAlarmAllowed: Boolean? = false, + val fcmToken: String? = "", + val memberLckYears: Int = 0, + val memberFanTeam: String? = "", + val memberDefaultProfileImage: String? = "" +) diff --git a/core/network/src/main/java/com/teamwable/network/datasource/ProfileService.kt b/core/network/src/main/java/com/teamwable/network/datasource/ProfileService.kt index f3d4d84a..4bd137e0 100644 --- a/core/network/src/main/java/com/teamwable/network/datasource/ProfileService.kt +++ b/core/network/src/main/java/com/teamwable/network/datasource/ProfileService.kt @@ -5,9 +5,12 @@ import com.teamwable.network.dto.response.ResponseProfileInfoDto import com.teamwable.network.dto.response.profile.ResponseMemberDataDto import com.teamwable.network.util.BaseResponse import com.teamwable.network.util.BaseUnitResponse +import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PATCH +import retrofit2.http.Part import retrofit2.http.Path interface ProfileService { @@ -21,4 +24,10 @@ interface ProfileService { @PATCH("api/v1/withdrawal") suspend fun patchWithdrawal(@Body requestWithdrawalDto: RequestWithdrawalDto): BaseUnitResponse + + @PATCH("api/v1/user-profile2") + suspend fun patchUserProfile( + @Part("info") requestProfileEdit: RequestBody, + @Part file: MultipartBody.Part?, + ): BaseUnitResponse } diff --git a/core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt b/core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt index a477cbce..0c370c52 100644 --- a/core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt +++ b/core/ui/src/main/java/com/teamwable/ui/extensions/FragmentExt.kt @@ -1,5 +1,8 @@ package com.teamwable.ui.extensions +import android.content.Intent +import android.net.Uri +import android.provider.Settings import android.view.View import android.widget.Toast import androidx.annotation.ColorRes @@ -48,3 +51,7 @@ fun Fragment.statusBarColorOf( ) { requireActivity().statusBarColorOf(resId) } + +fun Fragment.openUri(uri: String) { + Intent(Intent.ACTION_VIEW, Uri.parse(uri)).also { startActivity(it) } +} diff --git a/feature/news/src/main/java/com/teamwable/news/rank/NewsRankFragment.kt b/feature/news/src/main/java/com/teamwable/news/rank/NewsRankFragment.kt index c3b0ecd7..11673f55 100644 --- a/feature/news/src/main/java/com/teamwable/news/rank/NewsRankFragment.kt +++ b/feature/news/src/main/java/com/teamwable/news/rank/NewsRankFragment.kt @@ -1,5 +1,7 @@ package com.teamwable.news.rank +import android.content.Intent +import android.net.Uri import android.text.Spannable import android.text.SpannableString import android.text.style.ForegroundColorSpan @@ -11,6 +13,7 @@ import com.teamwable.news.NewsViewModel import com.teamwable.news.databinding.FragmentNewsRankBinding import com.teamwable.ui.base.BindingFragment import com.teamwable.ui.extensions.colorOf +import com.teamwable.ui.extensions.openUri import com.teamwable.ui.extensions.viewLifeCycle import com.teamwable.ui.extensions.viewLifeCycleScope import dagger.hilt.android.AndroidEntryPoint @@ -46,7 +49,7 @@ class NewsRankFragment : BindingFragment(FragmentNewsRa private fun initOpinionBtnClickListener() { binding.btnNewsRankOpinion.setOnClickListener { - // Todo : 나중에 추가해야 함 + openUri("https://forms.gle/WWfbHXvGNgXMxgZr5") } } diff --git a/feature/profile/build.gradle.kts b/feature/profile/build.gradle.kts index 1bab26af..f0426aed 100644 --- a/feature/profile/build.gradle.kts +++ b/feature/profile/build.gradle.kts @@ -22,4 +22,6 @@ dependencies { // Third Party implementation(libs.glide) + implementation(libs.google.play.services) + implementation(libs.firebase.messaging.ktx) } diff --git a/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileHamburgerBottomSheet.kt b/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileHamburgerBottomSheet.kt index 414623d2..625fb3ff 100644 --- a/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileHamburgerBottomSheet.kt +++ b/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileHamburgerBottomSheet.kt @@ -8,6 +8,7 @@ import com.teamwable.profile.R import com.teamwable.profile.databinding.BottomsheetProfileHamburgerBinding import com.teamwable.ui.base.BindingBottomSheetFragment import com.teamwable.ui.component.TwoButtonDialog +import com.teamwable.ui.extensions.openUri import com.teamwable.ui.extensions.viewLifeCycleScope import com.teamwable.ui.type.DialogType import com.teamwable.ui.util.Arg.DIALOG_RESULT @@ -42,13 +43,13 @@ class ProfileHamburgerBottomSheet : BindingBottomSheetFragment>(UiState.Loading) val withdrawalUiState = _withdrawalUiState.asStateFlow() + private var _pushAlarmAllowedState = MutableStateFlow(false) + val pushAlarmAllowedState = _pushAlarmAllowedState.asStateFlow() + fun getMemberData() { viewModelScope.launch { _memberDataUiState.value = UiState.Loading @@ -50,4 +56,18 @@ class ProfileHamburgerViewModel @Inject constructor( userInfoRepository.saveAutoLogin(check) } } + + fun patchUserProfileUri(info: MemberInfoEditModel, url: String? = null) { + viewModelScope.launch { + profileRepository.patchProfileUriEdit(info, url) + .onSuccess { info.isPushAlarmAllowed?.let { _pushAlarmAllowedState.value = it } } + .onFailure { Timber.d("fail", it.message.toString()) } + } + } + + fun saveIsPushAlarmAllowed(isPushAlarmAllowed: Boolean) = + viewModelScope.launch { + userInfoRepository.saveIsPushAlarmAllowed(isPushAlarmAllowed) + } + } diff --git a/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileInformationFragment.kt b/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileInformationFragment.kt index 9fe29cba..d3587d76 100644 --- a/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileInformationFragment.kt +++ b/feature/profile/src/main/java/com/teamwable/profile/hamburger/ProfileInformationFragment.kt @@ -10,6 +10,7 @@ import com.teamwable.model.profile.MemberDataModel import com.teamwable.profile.R import com.teamwable.profile.databinding.FragmentProfileInformationBinding import com.teamwable.ui.base.BindingFragment +import com.teamwable.ui.extensions.openUri import com.teamwable.ui.extensions.stringOf import com.teamwable.ui.extensions.viewLifeCycle import com.teamwable.ui.extensions.viewLifeCycleScope @@ -35,14 +36,10 @@ class ProfileInformationFragment : BindingFragment(FragmentPushAlarmBinding::inflate) { + private val viewModel by viewModels() + + override fun initView() { + setAppbarText() + setPushAlarmText() + + initPushAlarmSettingClickListener() + initBackBtnClickListener() + + setupUserPushAlarmInfoObserve() + } + + override fun onResume() { + super.onResume() + refreshPushAlarmPermission() + } + + private fun refreshPushAlarmPermission() { + setPushAlarmText() + when (checkPushAlarmAllowed()) { + true -> handlePushAlarmPermissionGranted() + false -> handlePushAlarmPermissionDenied() + } + } + + private fun handlePushAlarmPermissionGranted() { + FirebaseMessaging.getInstance().token.addOnCompleteListener( + OnCompleteListener { task -> + if (task.isSuccessful) { + viewModel.patchUserProfileUri( + MemberInfoEditModel( + isPushAlarmAllowed = true, + fcmToken = task.result + ) + ) + Timber.tag("fcm").d("fcm token: $task.result") + } else { + Timber.d(task.exception) + return@OnCompleteListener + } + } + ) + } + + private fun handlePushAlarmPermissionDenied() { + viewModel.patchUserProfileUri(MemberInfoEditModel(isPushAlarmAllowed = false)) + } + + private fun setupUserPushAlarmInfoObserve() { + viewModel.pushAlarmAllowedState.flowWithLifecycle(viewLifeCycle).onEach { + viewModel.saveIsPushAlarmAllowed(it) + }.launchIn(viewLifeCycleScope) + } + + private fun initPushAlarmSettingClickListener() { + binding.tvPushAlarmContent.setOnClickListener { + requireContext().navigateToAppSettings() + } + binding.btnPushAlarmMore.setOnClickListener { + requireContext().navigateToAppSettings() + } + } + + + private fun setPushAlarmText() { + binding.tvPushAlarmContent.text = + if (checkPushAlarmAllowed()) getString(R.string.tv_push_alarm_content_on) + else getString(R.string.tv_push_alarm_content_off) + } + + private fun checkPushAlarmAllowed(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() + } + } + + private fun setAppbarText() { + binding.viewPushAlarmAppbar.tvProfileAppbarTitle.text = stringOf(R.string.appbar_push_alarm_title) + } + + private fun initBackBtnClickListener() { + binding.viewPushAlarmAppbar.btnProfileAppbarBack.setOnClickListener { + findNavController().popBackStack() + } + } +} diff --git a/feature/profile/src/main/java/com/teamwable/profile/hamburger/PushNotificationFragment.kt b/feature/profile/src/main/java/com/teamwable/profile/hamburger/PushNotificationFragment.kt deleted file mode 100644 index f2954b96..00000000 --- a/feature/profile/src/main/java/com/teamwable/profile/hamburger/PushNotificationFragment.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.teamwable.profile.hamburger - -import androidx.navigation.fragment.findNavController -import com.teamwable.profile.R -import com.teamwable.profile.databinding.FragmentPushNotificationBinding -import com.teamwable.ui.base.BindingFragment -import com.teamwable.ui.extensions.stringOf - -class PushNotificationFragment : BindingFragment(FragmentPushNotificationBinding::inflate) { - override fun initView() { - setAppbarText() - initBackBtnClickListener() - } - - private fun setAppbarText() { - binding.viewPushNotificationAppbar.tvProfileAppbarTitle.text = stringOf(R.string.appbar_push_notification_title) - } - - private fun initBackBtnClickListener() { - binding.viewPushNotificationAppbar.btnProfileAppbarBack.setOnClickListener { - findNavController().popBackStack() - } - } -} diff --git a/feature/profile/src/main/res/layout/fragment_push_notification.xml b/feature/profile/src/main/res/layout/fragment_push_alarm.xml similarity index 79% rename from feature/profile/src/main/res/layout/fragment_push_notification.xml rename to feature/profile/src/main/res/layout/fragment_push_alarm.xml index 06da45f4..0ef1418d 100644 --- a/feature/profile/src/main/res/layout/fragment_push_notification.xml +++ b/feature/profile/src/main/res/layout/fragment_push_alarm.xml @@ -5,23 +5,23 @@ android:layout_height="match_parent"> + app:layout_constraintTop_toBottomOf="@id/view_push_alarm_appbar"> + tools:layout="@layout/fragment_push_alarm" /> diff --git a/feature/profile/src/main/res/values/strings.xml b/feature/profile/src/main/res/values/strings.xml index 6a301355..279f7d11 100644 --- a/feature/profile/src/main/res/values/strings.xml +++ b/feature/profile/src/main/res/values/strings.xml @@ -39,11 +39,11 @@ 계정 삭제하기 계정 정보 - - 푸시 알림 - on - off - 알림 설정 + + 푸시 알림 + on + off + 알림 설정 레벨입니다. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0328ceb..1fb3ddb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,6 +69,7 @@ androidxComposeMaterial3 = "1.2.1" # # https://developer.android.com/jetpack/androidx/releases/test androidxTestExt = "1.1.5" hiltNavigationComposeVersion = "1.2.0" +firebaseMessagingKtx = "24.0.1" [libraries] # kotlin @@ -161,6 +162,7 @@ androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navig # datastore androidx-datastore-core = { group = "androidx.datastore", name = "datastore", version.ref = "androidx-datastore" } androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidx-datastore" } +firebase-messaging-ktx = { group = "com.google.firebase", name = "firebase-messaging-ktx", version.ref = "firebaseMessagingKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }