diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/MainActivity.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/MainActivity.kt index 74615f225..7ef008078 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/MainActivity.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/MainActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.Bundle +import android.provider.Settings import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.core.content.ContextCompat @@ -29,24 +30,29 @@ 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.context.wineySnackbar +import org.go.sopt.winey.util.view.snackbar.SnackbarType import org.go.sopt.winey.util.view.UiState @AndroidEntryPoint class MainActivity : BindingActivity(R.layout.activity_main) { private val mainViewModel by viewModels() + private val isUploadSuccess by lazy { intent.extras?.getBoolean(EXTRA_UPLOAD_KEY, false) } private val isDeleteSuccess by lazy { intent.extras?.getBoolean(EXTRA_DELETE_KEY, false) } - private val isReportSuccess by lazy { intent.extras?.getBoolean(EXTRA_REPORT_KEY, false) } private val prevScreenName by lazy { intent.extras?.getString(KEY_PREV_SCREEN, "") } + private val notiType by lazy { intent.extras?.getString(KEY_NOTI_TYPE, "") } private val feedId by lazy { intent.extras?.getString(KEY_FEED_ID) } + private val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (!isGranted) { wineySnackbar( anchorView = binding.root, - isSuccess = false, - message = stringOf(R.string.snackbar_notification_permission_fail) + message = stringOf(R.string.snackbar_noti_permission_denied), + type = SnackbarType.NotiPermission( + onActionClicked = { showSystemNotificationSetting() } + ) ) } } @@ -59,13 +65,23 @@ class MainActivity : BindingActivity(R.layout.activity_main // 위니피드, 마이페이지 프래그먼트에서 getUserState 관찰 mainViewModel.getUser() mainViewModel.patchFcmToken() + initNotiTypeHandler() initFragment() initBnvItemSelectedListener() syncBottomNavigationSelection() setupLogoutState() - showSuccessSnackBar() + showWineyFeedResultSnackBar() + } + + 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 requestNotificationPermission() { @@ -91,6 +107,7 @@ class MainActivity : BindingActivity(R.layout.activity_main NotificationType.LIKE_NOTIFICATION, NotificationType.COMMENT_NOTIFICATION -> navigateToDetail(feedId?.toInt()) + NotificationType.HOW_TO_LEVEL_UP -> navigateToLevelupHelp() else -> {} } @@ -108,13 +125,21 @@ class MainActivity : BindingActivity(R.layout.activity_main } } - private fun showSuccessSnackBar() { + private fun showWineyFeedResultSnackBar() { if (isUploadSuccess == true) { - wineySnackbar(binding.root, true, stringOf(R.string.snackbar_upload_success)) + wineySnackbar( + anchorView = binding.root, + message = stringOf(R.string.snackbar_upload_success), + type = SnackbarType.WineyFeedResult(isSuccess = true) + ) } if (isDeleteSuccess == true) { - wineySnackbar(binding.root, true, stringOf(R.string.snackbar_feed_delete_success)) + wineySnackbar( + anchorView = binding.root, + message = stringOf(R.string.snackbar_feed_delete_success), + type = SnackbarType.WineyFeedResult(isSuccess = true) + ) } } @@ -203,7 +228,6 @@ class MainActivity : BindingActivity(R.layout.activity_main companion object { private const val EXTRA_UPLOAD_KEY = "upload" private const val EXTRA_DELETE_KEY = "delete" - private const val EXTRA_REPORT_KEY = "report" private const val KEY_FEED_ID = "feedId" private const val KEY_NOTI_TYPE = "notiType" diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt index f43058999..e904286bb 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/WineyFeedFragment.kt @@ -46,6 +46,7 @@ import org.go.sopt.winey.util.fragment.stringOf import org.go.sopt.winey.util.fragment.viewLifeCycle import org.go.sopt.winey.util.fragment.viewLifeCycleScope import org.go.sopt.winey.util.fragment.wineySnackbar +import org.go.sopt.winey.util.view.snackbar.SnackbarType import org.go.sopt.winey.util.view.UiState import org.go.sopt.winey.util.view.WineyPopupMenu import org.go.sopt.winey.util.view.setOnSingleClickListener @@ -236,10 +237,11 @@ class WineyFeedFragment : deletePagingDataItem(response.feedId.toInt()) wineySnackbar( - binding.root, - true, - stringOf(R.string.snackbar_feed_delete_success) + anchorView = binding.root, + message = stringOf(R.string.snackbar_feed_delete_success), + type = SnackbarType.WineyFeedResult(isSuccess = true) ) + viewModel.initDeleteFeedState() } diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/detail/DetailActivity.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/detail/DetailActivity.kt index 12ec2be35..5a15abcae 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/detail/DetailActivity.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/detail/DetailActivity.kt @@ -34,6 +34,7 @@ import org.go.sopt.winey.util.context.snackBar import org.go.sopt.winey.util.context.stringOf import org.go.sopt.winey.util.context.wineySnackbar import org.go.sopt.winey.util.fragment.WineyDialogFragment +import org.go.sopt.winey.util.view.snackbar.SnackbarType import org.go.sopt.winey.util.view.UiState import org.go.sopt.winey.util.view.WineyPopupMenu import org.json.JSONException @@ -356,7 +357,11 @@ class DetailActivity : BindingActivity(R.layout.activity_ } is UiState.Failure -> { - wineySnackbar(binding.root, false, stringOf(R.string.snackbar_delete_fail)) + wineySnackbar( + anchorView = binding.root, + message = stringOf(R.string.snackbar_delete_fail), + type = SnackbarType.WineyFeedResult(isSuccess = false) + ) } else -> Timber.tag("failure").e(MSG_DETAIL_ERROR) @@ -409,18 +414,21 @@ class DetailActivity : BindingActivity(R.layout.activity_ } wineySnackbar( - binding.root, - true, - stringOf(R.string.snackbar_comment_delete_success) + anchorView = binding.root, + message = stringOf(R.string.snackbar_comment_delete_success), + type = SnackbarType.WineyFeedResult(isSuccess = true) ) } is UiState.Failure -> { - wineySnackbar(binding.root, false, stringOf(R.string.snackbar_delete_fail)) + wineySnackbar( + anchorView = binding.root, + message = stringOf(R.string.snackbar_delete_fail), + type = SnackbarType.WineyFeedResult(isSuccess = false) + ) } - else -> { - } + else -> {} } }.launchIn(lifecycleScope) } diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/AmountFragment.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/AmountFragment.kt index 0d38bd17b..33601b8db 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/AmountFragment.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/feed/upload/AmountFragment.kt @@ -20,6 +20,7 @@ import org.go.sopt.winey.util.fragment.viewLifeCycle import org.go.sopt.winey.util.fragment.viewLifeCycleScope import org.go.sopt.winey.util.fragment.wineySnackbar import org.go.sopt.winey.util.multipart.UriToRequestBody +import org.go.sopt.winey.util.view.snackbar.SnackbarType import org.go.sopt.winey.util.view.UiState import org.go.sopt.winey.util.view.setOnSingleClickListener import java.text.DecimalFormat @@ -68,7 +69,12 @@ class AmountFragment : BindingFragment(R.layout.fragment_ } is UiState.Failure -> { - wineySnackbar(binding.root, false, stringOf(R.string.snackbar_upload_fail)) + wineySnackbar( + anchorView = binding.root, + message = stringOf(R.string.snackbar_upload_fail), + type = SnackbarType.WineyFeedResult(isSuccess = false) + ) + uploadViewModel.initPostWineyFeedState() } diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt index 0665653f6..9bb261e85 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/MyPageFragment.kt @@ -1,8 +1,6 @@ package org.go.sopt.winey.presentation.main.mypage import android.Manifest -import android.content.ActivityNotFoundException -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri @@ -43,7 +41,6 @@ import org.go.sopt.winey.util.fragment.snackBar import org.go.sopt.winey.util.fragment.stringOf import org.go.sopt.winey.util.fragment.viewLifeCycle import org.go.sopt.winey.util.fragment.viewLifeCycleScope -import org.go.sopt.winey.util.fragment.wineySnackbar import org.go.sopt.winey.util.view.UiState import org.go.sopt.winey.util.view.setOnSingleClickListener import javax.inject.Inject @@ -58,184 +55,97 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ @Inject lateinit var amplitudeUtils: AmplitudeUtils - private var isNotificationPermissionAllowed = true + private var isNotiPermissionAllowed = true override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) amplitudeUtils.logEvent("view_mypage") - initCheckNotificationPermission() - initUserData() - initNavigation() + initNotiPermissionState() - init1On1ButtonClickListener() - initTermsButtonClickListener() - initLevelHelpButtonClickListener() - initToMyFeedButtonClickListener() - initLogoutButtonClickListener() - initWithdrawButtonClickListener() - initNicknameButtonClickListener() - initAllowedNotificationButtonClickListener() - initNotificationPermissionChangeButtonClickListener() + initUserData() + initClickListener() + setupObserver() + initNavigation() registerBackPressedCallback() - setupGetUserState() - setupDeleteUserState() - setupPatchAllowedNotificationState() - checkFromWineyFeed() } - private fun initCheckNotificationPermission() { - isNotificationPermissionAllowed = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } else { - true - } - } - - private fun setupPatchAllowedNotificationState() { - myPageViewModel.patchAllowedNotificationState.flowWithLifecycle(lifecycle).onEach { state -> - when (state) { - is UiState.Success -> { - when (state.data) { - true -> { - binding.ivMypageAgree.transitionToState(R.id.end, -1) - } - - false -> { - binding.ivMypageAgree.transitionToState(R.id.start, -1) - } - - null -> { - binding.ivMypageAgree.transitionToState(R.id.start, -1) - } - } - } - - else -> {} - } - } - } - - private fun initAllowedNotificationButtonClickListener() { - binding.ivMypageSwitch.setOnClickListener { - when (isNotificationPermissionAllowed) { - true -> { - val isAllowed = when (binding.ivMypageAgree.currentState) { - R.id.start -> false - R.id.end -> true - else -> false - } - when (isAllowed) { - true -> { - showNotificationOffDialog(isAllowed) - } + override fun onStart() { + super.onStart() - false -> { - binding.ivMypageAgree.transitionToState(R.id.end, -1) - patchUserInfo() - myPageViewModel.patchAllowedNotification(isAllowed) - } - } - } + // 닉네임 액티비티 갔다가 다시 돌아왔을 때 유저 데이터 갱신하도록 + mainViewModel.getUser() - false -> { - wineySnackbar( - binding.root, - true, - stringOf(R.string.snackbar_notification_permission_fail) - ) - } - } - } + // 시스템 설정창에서 유저가 설정한 권한에 따라 바로 뷰가 갱신되도록 + initNotiPermissionState() + updateNotiButtonByPermission() } - private fun showNotificationOffDialog(isAllowed: Boolean) { - 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 = { - binding.ivMypageAgree.transitionToState(R.id.start, -1) - patchUserInfo() - myPageViewModel.patchAllowedNotification(isAllowed) + private fun initNotiPermissionState() { + isNotiPermissionAllowed = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + true } - ) - dialog.show( - parentFragmentManager, - TAG_NOTIFICATION_OFF_DIALOG - ) } - private fun initNotificationPermissionChangeButtonClickListener() { - binding.llMypageAgreePermissionChange.setOnClickListener { - navigateToNotificationSetting(requireContext()) - } - } + private fun updateNotiButtonByPermission() { + if (isNotiPermissionAllowed) { + binding.ivMypageAgree.isVisible = true - private fun navigateToNotificationSetting(context: Context) { - val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - setNotificationIntentActionOreo(context) + binding.llMypageAgreePermissionChange.isGone = true + binding.tvMypageAgreePermission.isGone = true } else { - setNorificationIntentActionOreoLess(context) - } - try { - context.startActivity(intent) - } catch (e: ActivityNotFoundException) { - e.printStackTrace() + binding.ivMypageAgree.isGone = true + + binding.llMypageAgreePermissionChange.isVisible = true + binding.tvMypageAgreePermission.isVisible = true } } - 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 initClickListener() { + init1On1ButtonClickListener() + initTermsButtonClickListener() + initLevelHelpButtonClickListener() + initToMyFeedButtonClickListener() + initLogoutButtonClickListener() + initWithdrawButtonClickListener() + initNicknameButtonClickListener() + + initNotiToggleButtonClickListener() + initNotiPermissionButtonClickListener() } - 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 setupObserver() { + setupGetUserState() + setupDeleteUserState() + setupPatchAllowedNotificationState() } - private fun patchUserInfo() { - lifecycleScope.launch { + private fun initUserData() { + viewLifeCycleScope.launch { val data = dataStoreRepository.getUserInfo().first() - val newData = data?.copy(fcmIsAllowed = false) - dataStoreRepository.saveUserInfo(newData) + if (data != null) { + updateUserInfo(data) + initTargetModifyButtonClickListener(data) + } } } - // 닉네임 액티비티 갔다가 다시 돌아왔을 때 유저 데이터 갱신하도록 - override fun onStart() { - super.onStart() - mainViewModel.getUser() - initCheckNotificationPermission() - changeNotiButtonByPermission() - } - - private fun changeNotiButtonByPermission() { - if (isNotificationPermissionAllowed) { - binding.ivMypageAgree.isVisible = true - binding.llMypageAgreePermissionChange.isGone = true - binding.tvMypageAgreePermission.isGone = true - } else { - binding.ivMypageAgree.isGone = true - binding.llMypageAgreePermissionChange.isVisible = true - binding.tvMypageAgreePermission.isVisible = true + private fun initNavigation() { + val receivedBundle = arguments + if (receivedBundle != null) { + val value = receivedBundle.getBoolean(KEY_TO_MYFEED) + if (value) { + navigateAndBackStack() + arguments?.clear() + } } } @@ -266,13 +176,14 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) } - private fun initUserData() { - viewLifeCycleScope.launch { - val data = dataStoreRepository.getUserInfo().first() - if (data != null) { - updateUserInfo(data) - initTargetModifyButtonClickListener(data) - } + /** + * Listener 정의 + * */ + + private fun initToMyFeedButtonClickListener() { + binding.clMypageToMyfeed.setOnSingleClickListener { + amplitudeUtils.logEvent("click_myfeed") + navigateAndBackStack() } } @@ -283,21 +194,10 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ } } - private fun initToMyFeedButtonClickListener() { - binding.clMypageToMyfeed.setOnSingleClickListener { - amplitudeUtils.logEvent("click_myfeed") - navigateAndBackStack() - } - } - - private fun initNavigation() { - val receivedBundle = arguments - if (receivedBundle != null) { - val value = receivedBundle.getBoolean(KEY_TO_MYFEED) - if (value) { - navigateAndBackStack() - arguments?.clear() - } + private fun navigateToNicknameScreen() { + Intent(requireContext(), NicknameActivity::class.java).apply { + putExtra(KEY_PREV_SCREEN_NAME, VAL_MY_PAGE_SCREEN) + startActivity(this) } } @@ -358,39 +258,103 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ } } - private fun setupDeleteUserState() { - myPageViewModel.deleteUserState.flowWithLifecycle(viewLifeCycle) - .onEach { state -> - when (state) { - is UiState.Success -> { - myPageViewModel.clearDataStore() - navigateToGuideScreen() - } + private fun initTargetModifyButtonClickListener(user: User) { + binding.clMypageTargetmoney.setOnSingleClickListener { + amplitudeUtils.logEvent("click_goalsetting") - is UiState.Failure -> { - snackBar(binding.root) { state.msg } - } + // 목표를 설정한 적 없거나, 기간이 종료되었거나, 기간 내 목표를 달성한 경우 + // 바텀 시트 활성화 + if (user.isOver || user.isAttained) { + showTargetSettingBottomSheet() + } else { + showTargetNotOverDialog() + } + } + } - else -> { - } - } - }.launchIn(viewLifeCycleScope) + private fun showTargetSettingBottomSheet() { + val bottomSheet = TargetAmountBottomSheetFragment() + bottomSheet.show(parentFragmentManager, bottomSheet.tag) + amplitudeUtils.logEvent("view_goalsetting") } - private fun navigateToGuideScreen() { - Intent(requireContext(), GuideActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(this) + private fun showTargetNotOverDialog() { + val dialog = MyPageNotOverDialogFragment() + dialog.show(parentFragmentManager, dialog.tag) + } + + private fun initNotiToggleButtonClickListener() { + binding.ivMypageSwitch.setOnClickListener { + val isAllowed = when (binding.ivMypageAgree.currentState) { + R.id.start -> false + R.id.end -> true + else -> false + } + + if (!isAllowed) { + switchOnNotification() + } else { + showNotificationOffConfirmDialog() + } } } - private fun navigateToNicknameScreen() { - Intent(requireContext(), NicknameActivity::class.java).apply { - putExtra(KEY_PREV_SCREEN_NAME, VAL_MY_PAGE_SCREEN) + private fun switchOnNotification() { + binding.ivMypageAgree.transitionToState(R.id.end, -1) + patchUserInfo() + myPageViewModel.patchAllowedNotification(isAllowed = false) + } + + private fun switchOffNotification() { + binding.ivMypageAgree.transitionToState(R.id.start, -1) + patchUserInfo() + myPageViewModel.patchAllowedNotification(isAllowed = true) + } + + 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( + parentFragmentManager, + TAG_NOTIFICATION_OFF_DIALOG + ) + } + + private fun initNotiPermissionButtonClickListener() { + binding.llMypageAgreePermissionChange.setOnClickListener { + showSystemNotificationSetting() + } + } + + private fun showSystemNotificationSetting() { + Intent().apply { + action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + putExtra(Settings.EXTRA_APP_PACKAGE, context?.packageName) + flags = Intent.FLAG_ACTIVITY_NEW_TASK startActivity(this) } } + private fun patchUserInfo() { + lifecycleScope.launch { + val data = dataStoreRepository.getUserInfo().first() + val newData = data?.copy(fcmIsAllowed = false) + dataStoreRepository.saveUserInfo(newData) + } + } + + /** + * Observer 등록 + * */ + private fun setupGetUserState() { mainViewModel.getUserState.flowWithLifecycle(lifecycle).onEach { state -> when (state) { @@ -457,7 +421,7 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ } private fun updateNotificationAllowSwitchState(data: User) { - if (isNotificationPermissionAllowed) { + if (isNotiPermissionAllowed) { binding.ivMypageAgree.isVisible = true binding.llMypageAgreePermissionChange.isGone = true binding.tvMypageAgreePermission.isGone = true @@ -477,29 +441,54 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ } } - private fun initTargetModifyButtonClickListener(user: User) { - binding.clMypageTargetmoney.setOnSingleClickListener { - amplitudeUtils.logEvent("click_goalsetting") + private fun setupDeleteUserState() { + myPageViewModel.deleteUserState.flowWithLifecycle(viewLifeCycle) + .onEach { state -> + when (state) { + is UiState.Success -> { + myPageViewModel.clearDataStore() + navigateToGuideScreen() + } - // 목표를 설정한 적 없거나, 기간이 종료되었거나, 기간 내 목표를 달성한 경우 - // 바텀 시트 활성화 - if (user.isOver || user.isAttained) { - showTargetSettingBottomSheet() - } else { - showTargetNotOverDialog() - } - } + is UiState.Failure -> { + snackBar(binding.root) { state.msg } + } + + else -> { + } + } + }.launchIn(viewLifeCycleScope) } - private fun showTargetSettingBottomSheet() { - val bottomSheet = TargetAmountBottomSheetFragment() - bottomSheet.show(parentFragmentManager, bottomSheet.tag) - amplitudeUtils.logEvent("view_goalsetting") + private fun navigateToGuideScreen() { + Intent(requireContext(), GuideActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(this) + } } - private fun showTargetNotOverDialog() { - val dialog = MyPageNotOverDialogFragment() - dialog.show(parentFragmentManager, dialog.tag) + private fun setupPatchAllowedNotificationState() { + myPageViewModel.patchAllowedNotificationState.flowWithLifecycle(lifecycle).onEach { state -> + when (state) { + is UiState.Success -> { + when (state.data) { + true -> { + binding.ivMypageAgree.transitionToState(R.id.end, -1) + } + + false -> { + binding.ivMypageAgree.transitionToState(R.id.start, -1) + } + + null -> { + binding.ivMypageAgree.transitionToState(R.id.start, -1) + } + } + } + + else -> {} + } + } } private inline fun navigateAndBackStack() { diff --git a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/myfeed/MyFeedFragment.kt b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/myfeed/MyFeedFragment.kt index f2fdbf8db..dcf2732cb 100644 --- a/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/myfeed/MyFeedFragment.kt +++ b/app/src/main/java/org/go/sopt/winey/presentation/main/mypage/myfeed/MyFeedFragment.kt @@ -32,6 +32,7 @@ import org.go.sopt.winey.util.fragment.stringOf import org.go.sopt.winey.util.fragment.viewLifeCycle import org.go.sopt.winey.util.fragment.viewLifeCycleScope import org.go.sopt.winey.util.fragment.wineySnackbar +import org.go.sopt.winey.util.view.snackbar.SnackbarType import org.go.sopt.winey.util.view.UiState import org.go.sopt.winey.util.view.WineyPopupMenu import timber.log.Timber @@ -163,10 +164,11 @@ class MyFeedFragment : BindingFragment(R.layout.fragment_ deletePagingDataItem(response.feedId.toInt()) wineySnackbar( - binding.root, - true, - stringOf(R.string.snackbar_feed_delete_success) + anchorView = binding.root, + message = stringOf(R.string.snackbar_feed_delete_success), + type = SnackbarType.WineyFeedResult(isSuccess = true) ) + viewModel.initDeleteFeedState() } diff --git a/app/src/main/java/org/go/sopt/winey/util/context/ContextExt.kt b/app/src/main/java/org/go/sopt/winey/util/context/ContextExt.kt index e89c2ec08..07d5d0e2a 100644 --- a/app/src/main/java/org/go/sopt/winey/util/context/ContextExt.kt +++ b/app/src/main/java/org/go/sopt/winey/util/context/ContextExt.kt @@ -2,6 +2,7 @@ package org.go.sopt.winey.util.context import android.app.Activity import android.content.Context +import android.view.Gravity import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast @@ -10,7 +11,10 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat import com.google.android.material.snackbar.Snackbar -import org.go.sopt.winey.util.view.WineySnackbar +import org.go.sopt.winey.R +import org.go.sopt.winey.util.view.snackbar.NotiPermissionSnackbar +import org.go.sopt.winey.util.view.snackbar.SnackbarType +import org.go.sopt.winey.util.view.snackbar.WineyFeedResultSnackbar /** Hide keyboard from window */ fun Context.hideKeyboard(view: View) { @@ -30,8 +34,37 @@ fun Context.snackBar(anchorView: View, message: () -> String) { Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show() } -fun Context.wineySnackbar(anchorView: View, isSuccess: Boolean, message: String) { - WineySnackbar.make(anchorView, isSuccess, message).show() +fun Context.wineySnackbar( + anchorView: View, + message: String, + type: SnackbarType +) { + when (type) { + is SnackbarType.WineyFeedResult -> { + WineyFeedResultSnackbar( + anchorView = anchorView, + gravity = Gravity.TOP, + message = message + ).apply { + initResultIcon(isSuccess = type.isSuccess) + show() + } + } + + is SnackbarType.NotiPermission -> { + NotiPermissionSnackbar( + anchorView = anchorView, + gravity = Gravity.TOP, + message = message + ).apply { + setAction( + resId = R.string.snackbar_noti_permission_setting_text, + onClicked = type.onActionClicked + ) + show() + } + } + } } fun Context.stringOf(@StringRes resId: Int) = getString(resId) diff --git a/app/src/main/java/org/go/sopt/winey/util/fragment/FragmentExt.kt b/app/src/main/java/org/go/sopt/winey/util/fragment/FragmentExt.kt index aca98dd5c..f8c4a02de 100644 --- a/app/src/main/java/org/go/sopt/winey/util/fragment/FragmentExt.kt +++ b/app/src/main/java/org/go/sopt/winey/util/fragment/FragmentExt.kt @@ -1,5 +1,6 @@ package org.go.sopt.winey.util.fragment +import android.view.Gravity import android.view.View import android.widget.Toast import androidx.annotation.ColorRes @@ -9,7 +10,10 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar -import org.go.sopt.winey.util.view.WineySnackbar +import org.go.sopt.winey.R +import org.go.sopt.winey.util.view.snackbar.NotiPermissionSnackbar +import org.go.sopt.winey.util.view.snackbar.SnackbarType +import org.go.sopt.winey.util.view.snackbar.WineyFeedResultSnackbar fun Fragment.toast(message: String) { Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() @@ -23,8 +27,37 @@ fun Fragment.snackBar(anchorView: View, message: () -> String) { Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show() } -fun Fragment.wineySnackbar(anchorView: View, isSuccess: Boolean, message: String) { - WineySnackbar.make(anchorView, isSuccess, message).show() +fun Fragment.wineySnackbar( + anchorView: View, + message: String, + type: SnackbarType +) { + when (type) { + is SnackbarType.WineyFeedResult -> { + WineyFeedResultSnackbar( + anchorView = anchorView, + gravity = Gravity.TOP, + message = message + ).apply { + initResultIcon(isSuccess = type.isSuccess) + show() + } + } + + is SnackbarType.NotiPermission -> { + NotiPermissionSnackbar( + anchorView = anchorView, + gravity = Gravity.TOP, + message = message + ).apply { + setAction( + resId = R.string.snackbar_noti_permission_setting_text, + onClicked = type.onActionClicked + ) + show() + } + } + } } fun Fragment.stringOf(@StringRes resId: Int) = getString(resId) diff --git a/app/src/main/java/org/go/sopt/winey/util/view/snackbar/NotiPermissionSnackbar.kt b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/NotiPermissionSnackbar.kt new file mode 100644 index 000000000..b3e3b3137 --- /dev/null +++ b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/NotiPermissionSnackbar.kt @@ -0,0 +1,30 @@ +package org.go.sopt.winey.util.view.snackbar + +import android.graphics.Paint +import android.view.View +import androidx.annotation.StringRes +import androidx.core.view.isGone +import androidx.core.view.isVisible +import com.google.android.material.transition.SlideDistanceProvider.GravityFlag +import org.go.sopt.winey.util.context.stringOf + +class NotiPermissionSnackbar( + anchorView: View, + @GravityFlag gravity: Int, + message: String +) : WineySnackbar(anchorView, gravity, message) { + override fun initResultIcon(isSuccess: Boolean) { + binding.ivSnackbarResult.isGone = true + } + + override fun setAction(@StringRes resId: Int, onClicked: () -> Unit) { + binding.tvSnackbarAction.apply { + isVisible = true + paintFlags = Paint.UNDERLINE_TEXT_FLAG + text = context.stringOf(resId) + setOnClickListener { + onClicked.invoke() + } + } + } +} diff --git a/app/src/main/java/org/go/sopt/winey/util/view/snackbar/SnackbarType.kt b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/SnackbarType.kt new file mode 100644 index 000000000..2ff975485 --- /dev/null +++ b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/SnackbarType.kt @@ -0,0 +1,11 @@ +package org.go.sopt.winey.util.view.snackbar + +sealed class SnackbarType { + data class WineyFeedResult( + val isSuccess: Boolean + ) : SnackbarType() + + data class NotiPermission( + val onActionClicked: () -> Unit + ) : SnackbarType() +} diff --git a/app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineyFeedResultSnackbar.kt b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineyFeedResultSnackbar.kt new file mode 100644 index 000000000..ed64fd94b --- /dev/null +++ b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineyFeedResultSnackbar.kt @@ -0,0 +1,27 @@ +package org.go.sopt.winey.util.view.snackbar + +import android.view.View +import androidx.core.view.isVisible +import com.google.android.material.transition.SlideDistanceProvider.GravityFlag +import org.go.sopt.winey.R +import org.go.sopt.winey.util.context.drawableOf + +class WineyFeedResultSnackbar( + anchorView: View, + @GravityFlag gravity: Int, + message: String +) : WineySnackbar(anchorView, gravity, message) { + private val context = anchorView.context + + override fun initResultIcon(isSuccess: Boolean) { + if (isSuccess) { + binding.ivSnackbarResult.setImageDrawable(context.drawableOf(R.drawable.ic_snackbar_success)) + } else { + binding.ivSnackbarResult.setImageDrawable(context.drawableOf(R.drawable.ic_snackbar_fail)) + } + } + + override fun setAction(resId: Int, onClicked: () -> Unit) { + binding.tvSnackbarAction.isVisible = false + } +} diff --git a/app/src/main/java/org/go/sopt/winey/util/view/WineySnackbar.kt b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineySnackbar.kt similarity index 50% rename from app/src/main/java/org/go/sopt/winey/util/view/WineySnackbar.kt rename to app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineySnackbar.kt index 58c6814c8..9b0554a81 100644 --- a/app/src/main/java/org/go/sopt/winey/util/view/WineySnackbar.kt +++ b/app/src/main/java/org/go/sopt/winey/util/view/snackbar/WineySnackbar.kt @@ -1,37 +1,39 @@ -package org.go.sopt.winey.util.view +package org.go.sopt.winey.util.view.snackbar -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout +import androidx.annotation.StringRes import androidx.databinding.DataBindingUtil import com.google.android.material.snackbar.Snackbar +import com.google.android.material.transition.SlideDistanceProvider.GravityFlag import org.go.sopt.winey.R import org.go.sopt.winey.databinding.LayoutWineySnackbarBinding import org.go.sopt.winey.util.context.colorOf -import org.go.sopt.winey.util.context.drawableOf -class WineySnackbar( +abstract class WineySnackbar( anchorView: View, - private val isSuccess: Boolean, + @GravityFlag + private val gravity: Int, private val message: String ) { - private val context = anchorView.context private val snackbar = Snackbar.make(anchorView, "", DURATION_WINEY_SNACKBAR) private val snackbarLayout = snackbar.view as Snackbar.SnackbarLayout - - private val inflater = LayoutInflater.from(context) - private val binding: LayoutWineySnackbarBinding = + private val inflater = LayoutInflater.from(anchorView.context) + protected val binding: LayoutWineySnackbarBinding = DataBindingUtil.inflate(inflater, R.layout.layout_winey_snackbar, null, false) init { initView() - initData() } private fun initView() { + initLayout() setPosition() + initMessage() + } + private fun initLayout() { with(snackbarLayout) { removeAllViews() setPadding(0, 0, 0, 0) @@ -41,29 +43,24 @@ class WineySnackbar( } private fun setPosition() { - val snackbarLayoutParams = snackbar.view.layoutParams as FrameLayout.LayoutParams - snackbarLayoutParams.gravity = Gravity.TOP - snackbar.view.layoutParams = snackbarLayoutParams + val layoutParams = snackbar.view.layoutParams as FrameLayout.LayoutParams + layoutParams.gravity = gravity + snackbar.view.layoutParams = layoutParams } - private fun initData() { - if (isSuccess) { - binding.ivSnackbar.setImageDrawable(context.drawableOf(R.drawable.ic_snackbar_success)) - } else { - binding.ivSnackbar.setImageDrawable(context.drawableOf(R.drawable.ic_snackbar_fail)) - } - binding.tvSnackbar.text = message + private fun initMessage() { + binding.tvSnackbarMsg.text = message } fun show() { snackbar.show() } - companion object { - private const val DURATION_WINEY_SNACKBAR = 1500 + abstract fun initResultIcon(isSuccess: Boolean) + + abstract fun setAction(@StringRes resId: Int, onClicked: () -> Unit) - @JvmStatic - fun make(view: View, isSuccess: Boolean, message: String) = - WineySnackbar(view, isSuccess, message) + companion object { + private const val DURATION_WINEY_SNACKBAR = 2000 } } diff --git a/app/src/main/res/layout/layout_winey_snackbar.xml b/app/src/main/res/layout/layout_winey_snackbar.xml index 26ae7453e..6d3a0fd2e 100644 --- a/app/src/main/res/layout/layout_winey_snackbar.xml +++ b/app/src/main/res/layout/layout_winey_snackbar.xml @@ -7,15 +7,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8fb05fbfc..ba15b5f1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -227,7 +227,8 @@ 죄송합니다. 다시 시도해주세요. 정상적으로 신고되었습니다 :) 죄송합니다. 신고접수에 실패하였습니다. - 기기 설정을 변경해야 알림을 받을 수 있어요 + 기기 설정을 변경해야 알림을 받을 수 있어요 + 설정하기 LV. 평민 ㆍ