diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 942ebee8..c323c528 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -83,6 +83,10 @@
android:name=".presentation.main.notification.NotificationActivity"
android:exported="false"
android:screenOrientation="portrait" />
+
(R.layout.fragment_
private fun addListener() {
initEditNicknameButtonClickListener()
initMyFeedButtonClickListener()
+ initSettingButtonClickListener()
registerBackPressedCallback()
}
private fun addObserver() {
setupGetUserState()
- setupDeleteUserState()
+
+ checkFromWineyFeed()
}
private fun initCheckNotificationPermission() {
@@ -90,44 +89,6 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
}
- private fun navigateToNotificationSetting(context: Context) {
- val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- setNotificationIntentActionOreo(context)
- } else {
- setNorificationIntentActionOreoLess(context)
- }
- try {
- context.startActivity(intent)
- } catch (e: ActivityNotFoundException) {
- e.printStackTrace()
- }
- }
-
- private fun setNotificationIntentActionOreo(context: Context): Intent {
- return Intent().also { intent ->
- intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
- }
-
- private fun setNorificationIntentActionOreoLess(context: Context): Intent {
- return Intent().also { intent ->
- intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
- intent.putExtra("app_package", context.packageName)
- intent.putExtra("app_uid", context.applicationInfo?.uid)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
- }
-
- private fun patchUserInfo() {
- lifecycleScope.launch {
- val data = dataStoreRepository.getUserInfo().first()
- val newData = data?.copy(fcmIsAllowed = false)
- dataStoreRepository.saveUserInfo(newData)
- }
- }
-
// 닉네임 액티비티 갔다가 다시 돌아왔을 때 유저 데이터 갱신하도록
override fun onStart() {
super.onStart()
@@ -154,6 +115,12 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
}
+ private fun initSettingButtonClickListener() {
+ binding.ivMypageSetting.setOnClickListener {
+ navigateToSettingScreen()
+ }
+ }
+
// 마이페이지 왔다가 다시 알림 화면으로 돌아가도록
private fun registerBackPressedCallback() {
val callback = object : OnBackPressedCallback(true) {
@@ -193,25 +160,6 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
}
- private fun setupDeleteUserState() {
- myPageViewModel.deleteUserState.flowWithLifecycle(viewLifeCycle)
- .onEach { state ->
- when (state) {
- is UiState.Success -> {
- myPageViewModel.clearDataStore()
- navigateToGuideScreen()
- }
-
- is UiState.Failure -> {
- snackBar(binding.root) { state.msg }
- }
-
- else -> {
- }
- }
- }.launchIn(viewLifeCycleScope)
- }
-
private fun navigateToGuideScreen() {
Intent(requireContext(), GuideActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -226,6 +174,12 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
}
}
+ private fun navigateToSettingScreen() {
+ Intent(requireContext(), SettingActivity::class.java).apply {
+ startActivity(this)
+ }
+ }
+
private fun navigateToMyFeedScreen() {
Intent(requireContext(), MyFeedActivity::class.java).apply {
startActivity(this)
@@ -259,11 +213,6 @@ class MyPageFragment : BindingFragment(R.layout.fragment_
amplitudeUtils.logEvent("view_goalsetting")
}
- private fun showTargetNotOverDialog() {
- val dialog = MyPageNotOverDialogFragment()
- dialog.show(parentFragmentManager, dialog.tag)
- }
-
private inline fun navigateAndBackStack() {
parentFragmentManager.commit {
replace(R.id.fcv_main, T::class.simpleName)
diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageViewModel.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageViewModel.kt
index 6346e006..6bf730d8 100644
--- a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageViewModel.kt
+++ b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageViewModel.kt
@@ -22,9 +22,6 @@ class MyPageViewModel @Inject constructor(
private val _deleteUserState = MutableStateFlow>(UiState.Empty)
val deleteUserState: StateFlow> = _deleteUserState.asStateFlow()
- private val _patchAllowedNotificationState = MutableStateFlow>(UiState.Empty)
- val patchAllowedNotificationState: StateFlow> = _patchAllowedNotificationState.asStateFlow()
-
fun deleteUser() {
viewModelScope.launch {
authRepository.deleteUser()
@@ -44,30 +41,4 @@ class MyPageViewModel @Inject constructor(
}
}
}
-
- fun patchAllowedNotification(isAllowed: Boolean) {
- viewModelScope.launch {
- authRepository.patchAllowedNotification(!isAllowed)
- .onSuccess { response ->
- Timber.d("SUCCESS PATCH ALLOWED NOTI")
- _patchAllowedNotificationState.value = UiState.Success(response)
- }
- .onFailure { t ->
- _patchAllowedNotificationState.value = UiState.Failure(t.message.toString())
-
- if (t is HttpException) {
- Timber.e("HTTP FAIL ALLOWED NOTI : ${t.code()} ${t.message}")
- return@onFailure
- }
-
- Timber.e("FAIL ALLOWED NOTI : ${t.message}")
- }
- }
- }
-
- fun clearDataStore() {
- viewModelScope.launch {
- dataStoreRepository.clearDataStore()
- }
- }
}
diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingActivity.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingActivity.kt
new file mode 100644
index 00000000..f6c237ef
--- /dev/null
+++ b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingActivity.kt
@@ -0,0 +1,349 @@
+package org.go.sopt.winey.presentation.main.mypage.setting
+
+import android.Manifest
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import androidx.activity.viewModels
+import androidx.core.content.ContextCompat
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import org.go.sopt.winey.R
+import org.go.sopt.winey.databinding.ActivitySettingBinding
+import org.go.sopt.winey.domain.entity.UserV2
+import org.go.sopt.winey.domain.repository.DataStoreRepository
+import org.go.sopt.winey.presentation.main.MainViewModel
+import org.go.sopt.winey.presentation.model.WineyDialogLabel
+import org.go.sopt.winey.presentation.onboarding.guide.GuideActivity
+import org.go.sopt.winey.presentation.onboarding.login.LoginActivity
+import org.go.sopt.winey.util.amplitude.AmplitudeUtils
+import org.go.sopt.winey.util.binding.BindingActivity
+import org.go.sopt.winey.util.context.snackBar
+import org.go.sopt.winey.util.context.stringOf
+import org.go.sopt.winey.util.fragment.WineyDialogFragment
+import org.go.sopt.winey.util.view.UiState
+import org.go.sopt.winey.util.view.setOnSingleClickListener
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class SettingActivity : BindingActivity(R.layout.activity_setting) {
+ private val settingViewModel by viewModels()
+ private val mainViewModel by viewModels()
+
+ @Inject
+ lateinit var dataStoreRepository: DataStoreRepository
+
+ @Inject
+ lateinit var amplitudeUtils: AmplitudeUtils
+ private var isNotificationPermissionAllowed = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ addListener()
+ addObserver()
+ initNotificationPermissionState()
+ initUserData()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ initNotificationPermissionState()
+ updateNotificationButtonByPermission()
+ }
+
+ private fun addListener() {
+ init1On1ButtonClickListener()
+ initLogoutButtonClickListener()
+ initTermsButtonClickListener()
+ initWithdrawButtonClickListener()
+ initNotificationPermissionButtonClickListener()
+ initNotiToggleButtonClickListener()
+ initBackButtonClickListener()
+ }
+
+ private fun addObserver() {
+ setupDeleteUserState()
+ setupPatchAllowedNotificationState()
+ setupLogoutState()
+ }
+
+ private fun switchOnNotification() {
+ binding.ivSettingAgree.transitionToState(R.id.end, -1)
+ patchUserInfo()
+ settingViewModel.patchAllowedNotification(isAllowed = false)
+ }
+
+ private fun switchOffNotification() {
+ binding.ivSettingAgree.transitionToState(R.id.start, -1)
+ patchUserInfo()
+ settingViewModel.patchAllowedNotification(isAllowed = true)
+ }
+
+ private fun initNotificationPermissionButtonClickListener() {
+ binding.llSettingAgreePermissionChange.setOnClickListener {
+ showSystemNotificationSetting()
+ }
+ }
+
+ private fun showSystemNotificationSetting() {
+ Intent().apply {
+ action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
+ putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ startActivity(this)
+ }
+ }
+
+ private fun showNotificationOffConfirmDialog() {
+ val dialog = WineyDialogFragment.newInstance(
+ WineyDialogLabel(
+ stringOf(R.string.notification_off_dialog_title),
+ stringOf(R.string.notification_off_dialog_subtitle),
+ stringOf(R.string.notification_off_dialog_negative_button),
+ stringOf(R.string.notification_off_dialog_positive_button)
+ ),
+ handleNegativeButton = {},
+ handlePositiveButton = { switchOffNotification() }
+ )
+ dialog.show(
+ supportFragmentManager,
+ TAG_NOTIFICATION_OFF_DIALOG
+ )
+ }
+
+ private fun initUserData() {
+ lifecycleScope.launch {
+ val data = dataStoreRepository.getUserInfo().first()
+ if (data != null) {
+ updateNotificationAllowSwitchState(data)
+ }
+ }
+ }
+
+ private fun updateNotificationAllowSwitchState(data: UserV2) {
+ if (isNotificationPermissionAllowed) {
+ binding.ivSettingAgree.isVisible = true
+ binding.llSettingAgreePermissionChange.isGone = true
+ binding.tvSettingAgreePermission.isGone = true
+ when (data.fcmIsAllowed) {
+ true -> {
+ binding.ivSettingAgree.transitionToState(R.id.end, 1)
+ }
+
+ false -> {
+ binding.ivSettingAgree.transitionToState(R.id.start, 1)
+ }
+ }
+ } else {
+ binding.ivSettingAgree.isGone = true
+ binding.llSettingAgreePermissionChange.isVisible = true
+ binding.tvSettingAgreePermission.isVisible = true
+ }
+ }
+
+ private fun updateNotificationButtonByPermission() {
+ if (isNotificationPermissionAllowed) {
+ binding.ivSettingAgree.isVisible = true
+
+ binding.llSettingAgreePermissionChange.isGone = true
+ binding.tvSettingAgreePermission.isGone = true
+ } else {
+ binding.ivSettingAgree.isGone = true
+
+ binding.llSettingAgreePermissionChange.isVisible = true
+ binding.tvSettingAgreePermission.isVisible = true
+ }
+ }
+
+ private fun patchUserInfo() {
+ lifecycleScope.launch {
+ val data = dataStoreRepository.getUserInfo().first()
+ val newData = data?.copy(fcmIsAllowed = false)
+ dataStoreRepository.saveUserInfo(newData)
+ }
+ }
+
+ private fun initNotiToggleButtonClickListener() {
+ binding.ivSettingSwitch.setOnClickListener {
+ val isAllowed = when (binding.ivSettingAgree.currentState) {
+ R.id.start -> false
+ R.id.end -> true
+ else -> false
+ }
+
+ if (!isAllowed) {
+ switchOnNotification()
+ } else {
+ showNotificationOffConfirmDialog()
+ }
+ }
+ }
+
+ private fun initNotificationPermissionState() {
+ isNotificationPermissionAllowed =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ } else {
+ true
+ }
+ }
+
+ private fun init1On1ButtonClickListener() {
+ binding.clSettingTo1on1.setOnClickListener {
+ val url = ONE_ON_ONE_URL
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ startActivity(intent)
+ }
+ }
+
+ private fun initTermsButtonClickListener() {
+ binding.clSettingToTerms.setOnClickListener {
+ val url = TERMS_URL
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ startActivity(intent)
+ }
+ }
+
+ private fun initBackButtonClickListener() {
+ binding.ivSettingBack.setOnSingleClickListener {
+ finish()
+ }
+ }
+
+ private fun initLogoutButtonClickListener() {
+ binding.clSettingLogout.setOnClickListener {
+ amplitudeUtils.logEvent("click_logout")
+ val dialog = WineyDialogFragment.newInstance(
+ WineyDialogLabel(
+ stringOf(R.string.mypage_logout_dialog_title),
+ stringOf(R.string.mypage_logout_dialog_subtitle),
+ stringOf(R.string.mypage_logout_dialog_negative_button),
+ stringOf(R.string.mypage_logout_dialog_positive_button)
+ ),
+ handleNegativeButton = {},
+ handlePositiveButton = { mainViewModel.postLogout() }
+ )
+ dialog.show(supportFragmentManager, TAG_LOGOUT_DIALOG)
+ }
+ }
+
+ private fun initWithdrawButtonClickListener() {
+ binding.clSettingWithdraw.setOnClickListener {
+ val dialog = WineyDialogFragment.newInstance(
+ WineyDialogLabel(
+ stringOf(R.string.mypage_withdraw_dialog_title),
+ stringOf(R.string.mypage_withdraw_dialog_subtitle),
+ stringOf(R.string.mypage_withdraw_dialog_negative_button),
+ stringOf(R.string.mypage_withdraw_dialog_positive_button)
+ ),
+ handleNegativeButton = { settingViewModel.deleteUser() },
+ handlePositiveButton = {}
+ )
+ dialog.show(supportFragmentManager, TAG_WITHDRAW_DIALOG)
+ }
+ }
+
+ private fun setupDeleteUserState() {
+ settingViewModel.deleteUserState.flowWithLifecycle(lifecycle)
+ .onEach { state ->
+ when (state) {
+ is UiState.Success -> {
+ settingViewModel.clearDataStore()
+ navigateToGuideScreen()
+ }
+
+ is UiState.Failure -> {
+ snackBar(binding.root) { state.msg }
+ }
+
+ else -> {
+ }
+ }
+ }.launchIn(lifecycleScope)
+ }
+
+ private fun setupPatchAllowedNotificationState() {
+ settingViewModel.patchAllowedNotificationState.flowWithLifecycle(lifecycle)
+ .onEach { state ->
+ when (state) {
+ is UiState.Success -> {
+ when (state.data) {
+ true -> {
+ binding.ivSettingAgree.transitionToState(R.id.end, -1)
+ }
+
+ false -> {
+ binding.ivSettingAgree.transitionToState(R.id.start, -1)
+ }
+
+ null -> {
+ binding.ivSettingAgree.transitionToState(R.id.start, -1)
+ }
+ }
+ }
+
+ else -> {}
+ }
+ }
+ }
+
+ private fun setupLogoutState() {
+ mainViewModel.logoutState.flowWithLifecycle(lifecycle).onEach { state ->
+ when (state) {
+ is UiState.Loading -> {
+ }
+
+ is UiState.Success -> {
+ navigateToLoginScreen()
+ }
+
+ is UiState.Failure -> {
+ snackBar(binding.root) { state.msg }
+ }
+
+ is UiState.Empty -> {
+ }
+ }
+ }.launchIn(lifecycleScope)
+ }
+
+ private fun navigateToLoginScreen() {
+ Intent(this, LoginActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ startActivity(this)
+ finish()
+ }
+ }
+
+ private fun navigateToGuideScreen() {
+ Intent(this, GuideActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(this)
+ }
+ }
+
+ companion object {
+
+ private const val ONE_ON_ONE_URL = "https://open.kakao.com/o/s751Susf"
+ private const val TERMS_URL =
+ "https://empty-weaver-a9f.notion.site/iney-9dbfe130c7df4fb9a0903481c3e377e6?pvs=4"
+
+ private const val TAG_LOGOUT_DIALOG = "LOGOUT_DIALOG"
+ private const val TAG_WITHDRAW_DIALOG = "WITHDRAW_DIALOG"
+
+ private const val TAG_NOTIFICATION_OFF_DIALOG = "offNotification"
+ }
+}
diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingViewModel.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingViewModel.kt
new file mode 100644
index 00000000..f6ae5b5e
--- /dev/null
+++ b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/setting/SettingViewModel.kt
@@ -0,0 +1,75 @@
+package org.go.sopt.winey.presentation.main.mypage.setting
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import org.go.sopt.winey.domain.repository.AuthRepository
+import org.go.sopt.winey.domain.repository.DataStoreRepository
+import org.go.sopt.winey.util.view.UiState
+import retrofit2.HttpException
+import timber.log.Timber
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingViewModel @Inject constructor(
+ private val authRepository: AuthRepository,
+ private val dataStoreRepository: DataStoreRepository
+) : ViewModel() {
+
+ private val _deleteUserState = MutableStateFlow>(UiState.Empty)
+ val deleteUserState: StateFlow> = _deleteUserState.asStateFlow()
+
+ private val _patchAllowedNotificationState = MutableStateFlow>(UiState.Empty)
+ val patchAllowedNotificationState: StateFlow> =
+ _patchAllowedNotificationState.asStateFlow()
+
+ fun deleteUser() {
+ viewModelScope.launch {
+ authRepository.deleteUser()
+ .onSuccess { response ->
+ Timber.d("SUCCESS DELETE USER")
+ _deleteUserState.value = UiState.Success(response)
+ }
+ .onFailure { t ->
+ _deleteUserState.value = UiState.Failure(t.message.toString())
+
+ if (t is HttpException) {
+ Timber.e("HTTP FAIL DELETE USER: ${t.code()} ${t.message}")
+ return@onFailure
+ }
+
+ Timber.e("FAIL DELETE USER: ${t.message}")
+ }
+ }
+ }
+
+ fun patchAllowedNotification(isAllowed: Boolean) {
+ viewModelScope.launch {
+ authRepository.patchAllowedNotification(!isAllowed)
+ .onSuccess { response ->
+ Timber.d("SUCCESS PATCH ALLOWED NOTI")
+ _patchAllowedNotificationState.value = UiState.Success(response)
+ }
+ .onFailure { t ->
+ _patchAllowedNotificationState.value = UiState.Failure(t.message.toString())
+
+ if (t is HttpException) {
+ Timber.e("HTTP FAIL ALLOWED NOTI : ${t.code()} ${t.message}")
+ return@onFailure
+ }
+
+ Timber.e("FAIL ALLOWED NOTI : ${t.message}")
+ }
+ }
+ }
+
+ fun clearDataStore() {
+ viewModelScope.launch {
+ dataStoreRepository.clearDataStore()
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml
new file mode 100644
index 00000000..7a62a270
--- /dev/null
+++ b/app/src/main/res/layout/activity_setting.xml
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_my_page.xml b/app/src/main/res/layout/fragment_my_page.xml
index 8979f5a4..eadbd331 100644
--- a/app/src/main/res/layout/fragment_my_page.xml
+++ b/app/src/main/res/layout/fragment_my_page.xml
@@ -26,6 +26,7 @@
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/xml/mypage_noti_agree_motion_scene.xml b/app/src/main/res/xml/mypage_noti_agree_motion_scene.xml
index 3a56fd20..835624b8 100644
--- a/app/src/main/res/xml/mypage_noti_agree_motion_scene.xml
+++ b/app/src/main/res/xml/mypage_noti_agree_motion_scene.xml
@@ -24,12 +24,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
- motion:layout_constraintBottom_toBottomOf="@+id/iv_mypage_switch"
- motion:layout_constraintStart_toStartOf="@+id/iv_mypage_switch"
- motion:layout_constraintTop_toTopOf="@+id/iv_mypage_switch"/>
+ motion:layout_constraintBottom_toBottomOf="@+id/iv_setting_switch"
+ motion:layout_constraintStart_toStartOf="@+id/iv_setting_switch"
+ motion:layout_constraintTop_toTopOf="@+id/iv_setting_switch"/>
+ motion:layout_constraintBottom_toBottomOf="@+id/iv_setting_switch"
+ motion:layout_constraintEnd_toEndOf="@+id/iv_setting_switch"
+ motion:layout_constraintTop_toTopOf="@+id/iv_setting_switch" />