diff --git a/app/src/main/java/com/daedan/festabook/di/FestaBookAppGraph.kt b/app/src/main/java/com/daedan/festabook/di/FestaBookAppGraph.kt index d75cf9de..8a29e5d8 100644 --- a/app/src/main/java/com/daedan/festabook/di/FestaBookAppGraph.kt +++ b/app/src/main/java/com/daedan/festabook/di/FestaBookAppGraph.kt @@ -6,8 +6,10 @@ import androidx.fragment.app.Fragment import com.daedan.festabook.FestaBookApp import com.daedan.festabook.di.viewmodel.MetroViewModelFactory import com.daedan.festabook.logging.DefaultFirebaseLogger -import com.daedan.festabook.presentation.main.MainActivity +import com.daedan.festabook.presentation.NotificationPermissionManager import com.daedan.festabook.presentation.placeDetail.PlaceDetailActivity +import com.daedan.festabook.presentation.placeDetail.PlaceDetailViewModel +import com.daedan.festabook.presentation.splash.AppVersionManager import com.daedan.festabook.presentation.splash.SplashActivity import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateManagerFactory @@ -26,8 +28,6 @@ interface FestaBookAppGraph { fun inject(app: FestaBookApp) - fun inject(activity: MainActivity) - fun inject(activity: SplashActivity) fun inject(activity: PlaceDetailActivity) @@ -40,6 +40,12 @@ interface FestaBookAppGraph { val defaultFirebaseLogger: DefaultFirebaseLogger val metroViewModelFactory: MetroViewModelFactory + + val notificationPermissionManagerFactory: NotificationPermissionManager.Factory + + val appVersionManagerFactory: AppVersionManager.Factory + + val placeDetailViewModelFactory: PlaceDetailViewModel.Factory } val Context.appGraph get() = (applicationContext as FestaBookApp).festaBookGraph diff --git a/app/src/main/java/com/daedan/festabook/presentation/FestabookScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/FestabookScreen.kt index 19002ac6..1d24f4fa 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/FestabookScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/FestabookScreen.kt @@ -2,69 +2,57 @@ package com.daedan.festabook.presentation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost -import com.daedan.festabook.logging.DefaultFirebaseLogger -import com.daedan.festabook.presentation.explore.ExploreViewModel import com.daedan.festabook.presentation.explore.navigation.exploreNavGraph import com.daedan.festabook.presentation.main.FestabookRoute -import com.daedan.festabook.presentation.main.MainViewModel import com.daedan.festabook.presentation.main.navigation.mainNavGraph import com.daedan.festabook.presentation.main.rememberFestabookNavigator -import com.daedan.festabook.presentation.news.NewsViewModel -import com.daedan.festabook.presentation.placeDetail.PlaceDetailViewModel -import com.daedan.festabook.presentation.setting.SettingViewModel -import com.daedan.festabook.presentation.splash.AppVersionManager +import com.daedan.festabook.presentation.platform.rememberAppGraph +import com.daedan.festabook.presentation.platform.rememberAppVersionManager +import com.daedan.festabook.presentation.platform.rememberLocationSource import com.daedan.festabook.presentation.splash.SplashViewModel import com.daedan.festabook.presentation.splash.navigation.splashNavGraph -import com.naver.maps.map.util.FusedLocationSource @Composable fun FestabookScreen( - appVersionManager: AppVersionManager, - notificationPermissionManager: NotificationPermissionManager, - placeDetailViewModelFactory: PlaceDetailViewModel.Factory, - defaultViewModelFactory: ViewModelProvider.Factory, - locationSource: FusedLocationSource, - logger: DefaultFirebaseLogger, + onAppFinish: () -> Unit, modifier: Modifier = Modifier, - newsViewModel: NewsViewModel = viewModel(), - mainViewModel: MainViewModel = viewModel(), - settingViewModel: SettingViewModel = viewModel(), splashViewModel: SplashViewModel = viewModel(), - exploreViewModel: ExploreViewModel = viewModel(), ) { + val appGraph = rememberAppGraph() + val locationSource = rememberLocationSource() val festabookNavigator = rememberFestabookNavigator() + val appVersionManager = + rememberAppVersionManager( + factory = appGraph.appVersionManagerFactory, + onUpdateSuccess = { splashViewModel.handleVersionCheckResult(Result.success(false)) }, + onUpdateFailure = { splashViewModel.handleVersionCheckResult(Result.failure(Exception("Update failed"))) }, + ) + NavHost( modifier = modifier, startDestination = festabookNavigator.startRoute, navController = festabookNavigator.navController, ) { splashNavGraph( - viewModel = splashViewModel, + appGraph = appGraph, appVersionManager = appVersionManager, onNavigateToExplore = { festabookNavigator.navigate(FestabookRoute.Explore) }, onNavigateToMain = { festabookNavigator.navigate(FestabookRoute.Main) }, - onFinishApp = { festabookNavigator.popBackStack() }, + onFinishApp = onAppFinish, ) exploreNavGraph( - viewModel = exploreViewModel, + appGraph = appGraph, onBackClick = { festabookNavigator.popBackStack() }, onNavigateToMain = { festabookNavigator.navigate(FestabookRoute.Main) }, ) mainNavGraph( - placeDetailViewModelFactory = placeDetailViewModelFactory, - defaultViewModelFactory = defaultViewModelFactory, - notificationPermissionManager = notificationPermissionManager, + appGraph = appGraph, + onAppFinish = onAppFinish, locationSource = locationSource, - logger = logger, - onSubscriptionConfirm = { festabookNavigator.navigate(FestabookRoute.Main) }, festabookNavigator = festabookNavigator, - settingViewModel = settingViewModel, - mainViewModel = mainViewModel, - newsViewModel = newsViewModel, ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/common/component/SnackBar.kt b/app/src/main/java/com/daedan/festabook/presentation/common/component/SnackBar.kt index 59caf1b3..244aa363 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/common/component/SnackBar.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/common/component/SnackBar.kt @@ -4,6 +4,7 @@ import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarData import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -34,6 +35,8 @@ class SnackbarManager( private val actionLabel: String, private val errorMessages: Map, String>, private val defaultErrorMessage: String, + private val permissionDeniedMessage: String, + private val moveToSettingLabel: String, ) { fun show(message: String) { hostState.currentSnackbarData?.dismiss() @@ -50,6 +53,21 @@ class SnackbarManager( val message = errorMessages[throwable::class] ?: defaultErrorMessage show(message) } + + fun showPermissionDeniedSnackbar(onOpenSettings: () -> Unit) { + hostState.currentSnackbarData?.dismiss() + scope.launch { + val result = + hostState.showSnackbar( + message = permissionDeniedMessage, + actionLabel = moveToSettingLabel, + duration = SnackbarDuration.Short, + ) + if (result == SnackbarResult.ActionPerformed) { + onOpenSettings() + } + } + } } @Composable @@ -62,6 +80,8 @@ fun rememberAppSnackbarManager( val networkErrorMessage = stringResource(R.string.error_network_exception) val unknownErrorMessage = stringResource(R.string.error_unknown_exception) val actionLabel = stringResource(R.string.fail_snackbar_confirm) + val permissionDeniedMessage = stringResource(R.string.notification_permission_denied_message) + val moveToSettingLabel = stringResource(R.string.move_to_setting_text) val errorMessages = remember { @@ -74,6 +94,14 @@ fun rememberAppSnackbarManager( } return remember(snackbarHostState, scope) { - SnackbarManager(snackbarHostState, scope, actionLabel, errorMessages, unknownErrorMessage) + SnackbarManager( + snackbarHostState, + scope, + actionLabel, + errorMessages, + unknownErrorMessage, + permissionDeniedMessage, + moveToSettingLabel, + ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/explore/navigation/ExploreNavigation.kt b/app/src/main/java/com/daedan/festabook/presentation/explore/navigation/ExploreNavigation.kt index 6a6ac820..12509313 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/explore/navigation/ExploreNavigation.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/explore/navigation/ExploreNavigation.kt @@ -1,19 +1,20 @@ package com.daedan.festabook.presentation.explore.navigation +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.daedan.festabook.presentation.explore.ExploreViewModel +import com.daedan.festabook.di.FestaBookAppGraph import com.daedan.festabook.presentation.explore.component.ExploreScreen import com.daedan.festabook.presentation.main.FestabookRoute fun NavGraphBuilder.exploreNavGraph( - viewModel: ExploreViewModel, + appGraph: FestaBookAppGraph, onBackClick: () -> Unit, onNavigateToMain: () -> Unit, ) { composable { ExploreScreen( - viewModel = viewModel, + viewModel = viewModel(factory = appGraph.metroViewModelFactory), onBackClick = onBackClick, onNavigateToMain = { onNavigateToMain() }, ) diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt index a7e3566d..5130ce54 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/component/HomeScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -25,6 +26,8 @@ import com.daedan.festabook.R import com.daedan.festabook.domain.model.Festival import com.daedan.festabook.domain.model.Organization import com.daedan.festabook.domain.model.Poster +import com.daedan.festabook.presentation.NotificationPermissionManager +import com.daedan.festabook.presentation.common.ObserveAsEvents import com.daedan.festabook.presentation.common.component.LoadingStateScreen import com.daedan.festabook.presentation.common.formatFestivalPeriod import com.daedan.festabook.presentation.home.HomeViewModel @@ -32,6 +35,7 @@ import com.daedan.festabook.presentation.home.LineUpItemGroupUiModel import com.daedan.festabook.presentation.home.LineupItemUiModel import com.daedan.festabook.presentation.home.LineupUiState import com.daedan.festabook.presentation.home.adapter.FestivalUiState +import com.daedan.festabook.presentation.setting.SettingViewModel import com.daedan.festabook.presentation.theme.FestabookColor import java.time.LocalDate import java.time.LocalDateTime @@ -39,13 +43,30 @@ import java.time.LocalDateTime @Composable fun HomeScreen( viewModel: HomeViewModel, + settingViewModel: SettingViewModel, + notificationPermissionManager: NotificationPermissionManager, onNavigateToExplore: () -> Unit, + onShowSnackBar: (String) -> Unit, + onShowErrorSnackbar: (Throwable) -> Unit, modifier: Modifier = Modifier, - onShowErrorSnackbar: (Throwable) -> Unit = {}, // TODO Fragment 제거 시 필수 파라미터로 변경 ) { + val context = LocalContext.current val festivalUiState by viewModel.festivalUiState.collectAsStateWithLifecycle() val lineupUiState by viewModel.lineupUiState.collectAsStateWithLifecycle() val currentOnShowErrorSnackbar by rememberUpdatedState(onShowErrorSnackbar) + + ObserveAsEvents(flow = settingViewModel.permissionCheckEvent) { + notificationPermissionManager.requestNotificationPermission(context) + } + + ObserveAsEvents(flow = settingViewModel.success) { + onShowSnackBar(context.getString(R.string.setting_notice_enabled)) + } + + ObserveAsEvents(flow = settingViewModel.error) { + currentOnShowErrorSnackbar(it) + } + LaunchedEffect(festivalUiState) { when (val state = festivalUiState) { is FestivalUiState.Error -> { diff --git a/app/src/main/java/com/daedan/festabook/presentation/home/navigation/HomeNavigation.kt b/app/src/main/java/com/daedan/festabook/presentation/home/navigation/HomeNavigation.kt index 5d97d185..f600d9ab 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/home/navigation/HomeNavigation.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/home/navigation/HomeNavigation.kt @@ -4,15 +4,20 @@ import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import com.daedan.festabook.presentation.NotificationPermissionManager import com.daedan.festabook.presentation.home.HomeViewModel import com.daedan.festabook.presentation.home.component.HomeScreen import com.daedan.festabook.presentation.main.MainTabRoute import com.daedan.festabook.presentation.main.MainViewModel import com.daedan.festabook.presentation.main.component.FirstVisitDialog +import com.daedan.festabook.presentation.setting.SettingViewModel fun NavGraphBuilder.homeNavGraph( viewModel: HomeViewModel, mainViewModel: MainViewModel, + settingViewModel: SettingViewModel, + notificationPermissionManager: NotificationPermissionManager, + onShowSnackbar: (String) -> Unit, onShowErrorSnackbar: (Throwable) -> Unit, onSubscriptionConfirm: () -> Unit, onNavigateToExplore: () -> Unit, @@ -29,6 +34,9 @@ fun NavGraphBuilder.homeNavGraph( viewModel = viewModel, onShowErrorSnackbar = onShowErrorSnackbar, onNavigateToExplore = onNavigateToExplore, + settingViewModel = settingViewModel, + notificationPermissionManager = notificationPermissionManager, + onShowSnackBar = onShowSnackbar, ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt index bd3d2071..6db17c33 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/MainActivity.kt @@ -6,117 +6,29 @@ import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.ViewModelProvider import com.daedan.festabook.R import com.daedan.festabook.di.appGraph import com.daedan.festabook.presentation.FestabookScreen -import com.daedan.festabook.presentation.NotificationPermissionManager -import com.daedan.festabook.presentation.NotificationPermissionRequester import com.daedan.festabook.presentation.common.isGranted import com.daedan.festabook.presentation.common.showNotificationDeniedSnackbar -import com.daedan.festabook.presentation.news.NewsViewModel -import com.daedan.festabook.presentation.placeDetail.PlaceDetailViewModel -import com.daedan.festabook.presentation.setting.SettingViewModel -import com.daedan.festabook.presentation.splash.AppVersionManager -import com.daedan.festabook.presentation.splash.SplashViewModel import com.daedan.festabook.presentation.theme.FestabookTheme -import com.naver.maps.map.util.FusedLocationSource -import dev.zacsweers.metro.Inject -import timber.log.Timber -class MainActivity : - AppCompatActivity(), - NotificationPermissionRequester { - @Inject - override lateinit var defaultViewModelProviderFactory: ViewModelProvider.Factory - - @Inject - private lateinit var viewModelFactory: PlaceDetailViewModel.Factory - - @Inject - private lateinit var notificationPermissionManagerFactory: NotificationPermissionManager.Factory - - @Inject - private lateinit var appVersionManagerFactory: AppVersionManager.Factory - - private val mainViewModel: MainViewModel by viewModels() - private val newsViewModel: NewsViewModel by viewModels() - private val settingViewModel: SettingViewModel by viewModels() - - private val splashViewModel: SplashViewModel by viewModels() - - private val notificationPermissionManager by lazy { - notificationPermissionManagerFactory.create( - onPermissionGranted = { onPermissionGranted() }, - onPermissionDenied = { onPermissionDenied() }, - shouldShowRationale = { shouldShowPermissionRationale(it) }, - launchPermission = { permissionLauncher.launch(it) }, - ) - } - - private val updateResultLauncher = - registerForActivityResult( - ActivityResultContracts.StartIntentSenderForResult(), - ) { result -> - if (result.resultCode == RESULT_OK) { - splashViewModel.handleVersionCheckResult(Result.success(false)) - } else { - splashViewModel.handleVersionCheckResult(Result.failure(Exception("Update failed"))) - } - } - - private val locationSource by lazy { - FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE) - } - - private val appVersionManager by lazy { appVersionManagerFactory.create(updateResultLauncher) } - - override val permissionLauncher: ActivityResultLauncher = - registerForActivityResult( - ActivityResultContracts.RequestPermission(), - ) { isGranted: Boolean -> - if (isGranted) { - Timber.d("Notification permission granted") - onPermissionGranted() - } else { - Timber.d("Notification permission denied") - showNotificationDeniedSnackbar(window.decorView.rootView, this) - onPermissionDenied() - } - } - - override fun onPermissionGranted() { - settingViewModel.saveNotificationId() - } - - override fun onPermissionDenied() = Unit +class MainActivity : AppCompatActivity() { + override val defaultViewModelProviderFactory: ViewModelProvider.Factory + get() = appGraph.metroViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { - appGraph.inject(this) super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - LaunchedEffect(Unit) { - handleNavigation(intent) - } - FestabookTheme { FestabookScreen( - notificationPermissionManager = notificationPermissionManager, - logger = appGraph.defaultFirebaseLogger, - locationSource = locationSource, - placeDetailViewModelFactory = viewModelFactory, - appVersionManager = appVersionManager, - defaultViewModelFactory = defaultViewModelProviderFactory, + onAppFinish = ::finish, ) } } - mainViewModel.registerDeviceAndFcmToken() } override fun onRequestPermissionsResult( @@ -131,6 +43,8 @@ class MainActivity : Manifest.permission.ACCESS_COARSE_LOCATION, -> { if (!result.isGranted()) { + // 이 부분은 레거시지만 NaverMap이 자동으로 권한 설정을 하기 때문에 + // 마이그래이션 하려면 MainActivity에 전역 State를 뚫어야 할 것 같습니다 showNotificationDeniedSnackbar( window.decorView.rootView, this, @@ -143,32 +57,10 @@ class MainActivity : super.onRequestPermissionsResult(requestCode, permissions, grantResults) } - override fun shouldShowPermissionRationale(permission: String): Boolean = shouldShowRequestPermissionRationale(permission) - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - handleNavigation(intent) - } - - private fun handleNavigation(intent: Intent) { - val noticeIdToExpand = intent.getLongExtra(KEY_NOTICE_ID_TO_EXPAND, INITIALIZED_ID) - if (noticeIdToExpand != INITIALIZED_ID) newsViewModel.expandNotice(noticeIdToExpand) - val canNavigateToNews = intent.getBooleanExtra(KEY_CAN_NAVIGATE_TO_NEWS, false) - if (canNavigateToNews) { - mainViewModel.navigateToNews() - } - } - companion object { - const val KEY_NOTICE_ID_TO_EXPAND = "noticeIdToExpand" - const val KEY_CAN_NAVIGATE_TO_NEWS = "canNavigateToNews" - const val LOCATION_PERMISSION_REQUEST_CODE = 1234 - - private const val INITIALIZED_ID = -1L - fun newIntent(context: Context) = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/component/MainScreen.kt b/app/src/main/java/com/daedan/festabook/presentation/main/component/MainScreen.kt index 72d0669a..f61b9063 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/component/MainScreen.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/component/MainScreen.kt @@ -1,11 +1,13 @@ package com.daedan.festabook.presentation.main.component +import android.content.Intent import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -14,7 +16,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.navigation.compose.NavHost import com.daedan.festabook.R -import com.daedan.festabook.logging.DefaultFirebaseLogger +import com.daedan.festabook.di.FestaBookAppGraph import com.daedan.festabook.presentation.NotificationPermissionManager import com.daedan.festabook.presentation.common.ObserveAsEvents import com.daedan.festabook.presentation.common.component.FestabookSnackbar @@ -35,6 +37,10 @@ import com.daedan.festabook.presentation.placeMap.PlaceMapViewModel import com.daedan.festabook.presentation.placeMap.component.PlaceMapRoute import com.daedan.festabook.presentation.placeMap.intent.event.SelectEvent import com.daedan.festabook.presentation.placeMap.navigation.placeMapNavGraph +import com.daedan.festabook.presentation.platform.DeepLinkKeys +import com.daedan.festabook.presentation.platform.RememberDeepLinkHandler +import com.daedan.festabook.presentation.platform.rememberNotificationPermissionManager +import com.daedan.festabook.presentation.platform.rememberOpenAppSettings import com.daedan.festabook.presentation.schedule.ScheduleViewModel import com.daedan.festabook.presentation.schedule.navigation.scheduleNavGraph import com.daedan.festabook.presentation.setting.SettingViewModel @@ -44,12 +50,9 @@ import com.naver.maps.map.util.FusedLocationSource @Composable @Suppress("ktlint:compose:vm-forwarding-check") fun MainScreen( - notificationPermissionManager: NotificationPermissionManager, - logger: DefaultFirebaseLogger, + appGraph: FestaBookAppGraph, locationSource: FusedLocationSource, - placeDetailViewModelFactory: PlaceDetailViewModel.Factory, onAppFinish: () -> Unit, - onSubscriptionConfirm: () -> Unit, festabookNavigator: FestabookNavigator, mainViewModel: MainViewModel, homeViewModel: HomeViewModel, @@ -63,6 +66,14 @@ fun MainScreen( val snackbarHostState = remember { SnackbarHostState() } val snackbarManager = rememberAppSnackbarManager(snackbarHostState) val backPressExitMessage = stringResource(R.string.back_press_exit_message) + val openAppSettings = rememberOpenAppSettings() + + val notificationPermissionManager = + rememberNotificationPermissionManager( + factory = appGraph.notificationPermissionManagerFactory, + onPermissionGrant = { settingViewModel.saveNotificationId() }, + onPermissionDeny = { snackbarManager.showPermissionDeniedSnackbar(openAppSettings) }, + ) ObserveAsEvents(flow = mainViewModel.navigateNewsEvent) { mainNavigator.navigateToMainTab(FestabookMainTab.NEWS) @@ -78,9 +89,17 @@ fun MainScreen( mainNavigator.navigateToMainTab(FestabookMainTab.SCHEDULE) } + LaunchedEffect(Unit) { + mainViewModel.registerDeviceAndFcmToken() + } + BackHandler { mainViewModel.onBackPressed() } + + RememberDeepLinkHandler { intent -> + handleNavigation(intent, newsViewModel, mainViewModel) + } Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) { data -> @@ -133,7 +152,7 @@ fun MainScreen( }, placeMapViewModel = placeMapViewModel, locationSource = locationSource, - logger = logger, + logger = appGraph.defaultFirebaseLogger, onShowErrorSnackBar = snackbarManager::showError, onStartPlaceDetail = { mainNavigator.navigate( @@ -151,10 +170,9 @@ fun MainScreen( homeViewModel = homeViewModel, scheduleViewModel = scheduleViewModel, settingViewModel = settingViewModel, - placeDetailViewModelFactory = placeDetailViewModelFactory, + placeDetailViewModelFactory = appGraph.placeDetailViewModelFactory, newsViewModel = newsViewModel, notificationPermissionManager = notificationPermissionManager, - onSubscriptionConfirm = onSubscriptionConfirm, snackbarManager = snackbarManager, ) } @@ -171,7 +189,6 @@ private fun FestabookNavHost( newsViewModel: NewsViewModel, settingViewModel: SettingViewModel, notificationPermissionManager: NotificationPermissionManager, - onSubscriptionConfirm: () -> Unit, snackbarManager: SnackbarManager, modifier: Modifier = Modifier, ) { @@ -184,8 +201,14 @@ private fun FestabookNavHost( viewModel = homeViewModel, mainViewModel = mainViewModel, onNavigateToExplore = { festabookNavigator.navigate(FestabookRoute.Explore) }, - onSubscriptionConfirm = onSubscriptionConfirm, + onSubscriptionConfirm = { + settingViewModel.notificationAllowClick() + mainViewModel.declineAlert() + }, + onShowSnackbar = snackbarManager::show, onShowErrorSnackbar = snackbarManager::showError, + settingViewModel = settingViewModel, + notificationPermissionManager = notificationPermissionManager, ) scheduleNavGraph( viewModel = scheduleViewModel, @@ -209,3 +232,15 @@ private fun FestabookNavHost( ) } } + +private fun handleNavigation( + intent: Intent, + newsViewModel: NewsViewModel, + mainViewModel: MainViewModel, +) { + val noticeIdToExpand = + intent.getLongExtra(DeepLinkKeys.KEY_NOTICE_ID_TO_EXPAND, DeepLinkKeys.INITIALIZED_ID) + if (noticeIdToExpand != DeepLinkKeys.INITIALIZED_ID) newsViewModel.expandNotice(noticeIdToExpand) + val canNavigateToNews = intent.getBooleanExtra(DeepLinkKeys.KEY_CAN_NAVIGATE_TO_NEWS, false) + if (canNavigateToNews) mainViewModel.navigateToNews() +} diff --git a/app/src/main/java/com/daedan/festabook/presentation/main/navigation/MainNavigation.kt b/app/src/main/java/com/daedan/festabook/presentation/main/navigation/MainNavigation.kt index 4863054c..f4cf3301 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/main/navigation/MainNavigation.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/main/navigation/MainNavigation.kt @@ -1,49 +1,34 @@ package com.daedan.festabook.presentation.main.navigation -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.daedan.festabook.logging.DefaultFirebaseLogger -import com.daedan.festabook.presentation.NotificationPermissionManager +import com.daedan.festabook.di.FestaBookAppGraph import com.daedan.festabook.presentation.main.FestabookNavigator import com.daedan.festabook.presentation.main.FestabookRoute -import com.daedan.festabook.presentation.main.MainViewModel import com.daedan.festabook.presentation.main.component.MainScreen -import com.daedan.festabook.presentation.news.NewsViewModel -import com.daedan.festabook.presentation.placeDetail.PlaceDetailViewModel -import com.daedan.festabook.presentation.setting.SettingViewModel import com.naver.maps.map.util.FusedLocationSource fun NavGraphBuilder.mainNavGraph( - defaultViewModelFactory: ViewModelProvider.Factory, - placeDetailViewModelFactory: PlaceDetailViewModel.Factory, - notificationPermissionManager: NotificationPermissionManager, + onAppFinish: () -> Unit, + appGraph: FestaBookAppGraph, locationSource: FusedLocationSource, - logger: DefaultFirebaseLogger, - onSubscriptionConfirm: () -> Unit, festabookNavigator: FestabookNavigator, - settingViewModel: SettingViewModel, - mainViewModel: MainViewModel, - newsViewModel: NewsViewModel, ) { composable { val mainBackEntry = festabookNavigator.navController.getBackStackEntry() MainScreen( - notificationPermissionManager = notificationPermissionManager, - logger = logger, + appGraph = appGraph, locationSource = locationSource, - placeDetailViewModelFactory = placeDetailViewModelFactory, - onAppFinish = festabookNavigator::popBackStack, - onSubscriptionConfirm = onSubscriptionConfirm, + onAppFinish = onAppFinish, festabookNavigator = festabookNavigator, - homeViewModel = viewModel(mainBackEntry, factory = defaultViewModelFactory), - scheduleViewModel = viewModel(mainBackEntry, factory = defaultViewModelFactory), - placeMapViewModel = viewModel(mainBackEntry, factory = defaultViewModelFactory), - settingViewModel = settingViewModel, - mainViewModel = mainViewModel, - newsViewModel = newsViewModel, + homeViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), + scheduleViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), + placeMapViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), + settingViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), + mainViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), + newsViewModel = viewModel(mainBackEntry, factory = appGraph.metroViewModelFactory), ) } } diff --git a/app/src/main/java/com/daedan/festabook/presentation/splash/navigation/SplashNavigation.kt b/app/src/main/java/com/daedan/festabook/presentation/splash/navigation/SplashNavigation.kt index 4a3b9b1f..03619e2c 100644 --- a/app/src/main/java/com/daedan/festabook/presentation/splash/navigation/SplashNavigation.kt +++ b/app/src/main/java/com/daedan/festabook/presentation/splash/navigation/SplashNavigation.kt @@ -1,14 +1,15 @@ package com.daedan.festabook.presentation.splash.navigation +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import com.daedan.festabook.di.FestaBookAppGraph import com.daedan.festabook.presentation.main.FestabookRoute import com.daedan.festabook.presentation.splash.AppVersionManager -import com.daedan.festabook.presentation.splash.SplashViewModel import com.daedan.festabook.presentation.splash.component.SplashScreen fun NavGraphBuilder.splashNavGraph( - viewModel: SplashViewModel, + appGraph: FestaBookAppGraph, appVersionManager: AppVersionManager, onNavigateToExplore: () -> Unit, onNavigateToMain: (Long) -> Unit, @@ -16,7 +17,7 @@ fun NavGraphBuilder.splashNavGraph( ) { composable { SplashScreen( - viewModel = viewModel, + viewModel = viewModel(factory = appGraph.metroViewModelFactory), appVersionManager = appVersionManager, onNavigateToExplore = onNavigateToExplore, onNavigateToMain = onNavigateToMain,