Проблема возникает, когда:
- я дважды быстро нажимаю на значок навигации, или
- я открываю панель с главного экрана, перехожу на экран настроек, затем нажимаю на навигацию значок снова после возвращения на главную страницу.
Вот GIF-изображение, демонстрирующее проблему.
Мой код:
Главный экран
@Composable
fun HomeScreen(
stateHomeScreen: StateHomeScreen,
event: (EventHomeScreen) -> Unit,
navController: NavController,
onMenuClick: () -> Unit,
networkStatus: NetworkStatus,
snackbarHostState: SnackbarHostState
) {
val scope = rememberCoroutineScope()
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.verticalScroll(state = rememberScrollState())
) {
HomeHeader(
onMenuClicked = { onMenuClick() },
onSettingsClicked = {
navController.navigate(Routes.SettingsScreen.route)
},
)
Spacer(modifier = Modifier.height(MediumSpacerHeight))
AppDropDownMenu(
menuName = "Number of Questions:",
menuList = Constants.numbersAsString,
text = stateHomeScreen.numberOfQuizzes.toString(),
onDropDownClick = {
event(EventHomeScreen.SetNumberOfQuizzes(it.toInt()))
})
AppDropDownMenu(
menuName = "Select Category:",
menuList = Constants.categories,
text = stateHomeScreen.category,
onDropDownClick = {
event(EventHomeScreen.SetQuizCategory(it))
})
AppDropDownMenu(
menuName = "Select of Difficulty:",
menuList = Constants.difficulty,
text = stateHomeScreen.difficulty,
onDropDownClick = {
event(EventHomeScreen.SetQuizDifficulty(it))
})
AppDropDownMenu(
menuName = "Select Type:", menuList = Constants.type,
text = stateHomeScreen.type,
onDropDownClick = {
event(EventHomeScreen.SetQuizType(it))
})
AdBannerView()
Spacer(Modifier.height(Dimens.SmallPadding))
ButtonBox(
"Generate Quiz", padding = MediumPadding
) {
if (networkStatus == NetworkStatus.Unavailable) {
scope.launch {
snackbarHostState.showSnackbar(
message = "
actionLabel = "Dismiss",
duration = SnackbarDuration.Long
)
}
return@ButtonBox
} else {
navController.navigate(
route = Routes.QuizScreen.passQuizParams(
stateHomeScreen.numberOfQuizzes,
stateHomeScreen.category,
difficulty = stateHomeScreen.difficulty,
type = stateHomeScreen.type
)
)
}
}
Spacer(modifier = Modifier.height(Dimens.LargePadding))
}
}
@Composable
fun ButtonBox(text: String, padding: Dp, onButtonClicked: () -> Unit) {
Button(
onClick = { onButtonClicked() },
modifier = Modifier
.fillMaxWidth()
.padding(padding)
) {
Text(
text, fontSize = Dimens.MediumTextSize,
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.SemiBold)
)
}
}
HomeHeader
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeHeader(
onMenuClicked: () -> Unit = {},
onSettingsClicked: () -> Unit = {}
) {
TopAppBar(
title = {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Quizzy",
fontSize = HeroTextSize,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Center
)
},
navigationIcon = {
IconButton(
onClick = {
Log.d("DRAWER_TEST", "Burger clicked!")
onMenuClicked()
},
modifier = Modifier
.size(40.dp)
.padding(start = 8.dp),
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Menu",
tint = MaterialTheme.colorScheme.secondary,
)
}
},
actions = {
IconButton(onClick = onSettingsClicked) {
Icon(
contentDescription = "Settings",
tint = colorResource(R.color.colorSecondary),
imageVector = Icons.Default.Settings,
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = colorResource(R.color.colorPrimaryVariant)
),
modifier = Modifier.clip(
RoundedCornerShape(
bottomStart = Dimens.LargeCornerRadius,
bottomEnd = Dimens.LargeCornerRadius
)
)
)
}
MainAppNavGraph
@Composable
fun MainAppNavGraph(
currentUser: FirebaseUser?, onLogout: () -> Unit,
networkStatus: NetworkStatus,
snackbarHostState: SnackbarHostState
) {
val navController = rememberNavController()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val context = LocalContext.current
val currentBackStack by navController.currentBackStackEntryAsState()
val currentRoute = currentBackStack?.destination?.route
val drawerItems = listOf(
DrawerItem("home", "Home", Icons.Default.Home, route = Routes.HomeScreen.route),
DrawerItem("profile", "Profile", Icons.Default.Person, route = Routes.ProfileScreen.route),
DrawerItem(
"leader_board",
"Leader Board",
Icons.Default.Leaderboard,
route = Routes.LeaderboardScreen.route
),
DrawerItem(
"remove_ads_for_30_mins",
"Remove Ads?",
iconRes = R.drawable.ad_blocker,
route = null
),
DrawerItem(
id = "settings",
title = "Settings",
iconImageVector = Icons.Default.Settings,
route = Routes.SettingsScreen.route
)
)
val authViewModel = hiltViewModel()
val userState by authViewModel.userData.collectAsState()
val quizViewModel = hiltViewModel()
var drawerGesturesEnabled by remember { mutableStateOf(true) }
val homeScreenViewModel = hiltViewModel()
val state by homeScreenViewModel.homeState.collectAsState()
var isDrawerOpening by remember { mutableStateOf(false) }
LaunchedEffect(currentUser?.uid) {
currentUser?.uid?.let {
authViewModel.loadUserData(it)
}
}
LaunchedEffect(currentRoute) {
drawerGesturesEnabled =
(currentRoute != Routes.PrivacyPolicyScreen.route && currentRoute != Routes.TermsAndConditionsScreen.route)
if (currentRoute == Routes.PrivacyPolicyScreen.route
|| currentRoute == Routes.TermsAndConditionsScreen.route
) {
scope.launch { drawerState.close() }
}
}
val userName = (userState as? Resource.Success)?.data?.userName ?: "No User Logged"
val userEmail =
(userState as? Resource.Success)?.data?.email ?: "No Email"
ModalNavigationDrawer(
drawerState = drawerState,
gesturesEnabled = drawerGesturesEnabled,
drawerContent = {
ModalDrawerSheet(
windowInsets = WindowInsets(0, 0, 0, 0),
drawerContainerColor = MaterialTheme.colorScheme.surface,
) {
AppDrawer(
currentRoute = currentRoute ?: Routes.HomeScreen.route,
userName = userName,
userEmail = userEmail,
drawerItems = drawerItems,
onItemClick = { item ->
item.route?.let {
navController.navigate(it) {
popUpTo(Routes.HomeScreen.route) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
},
onLogoutClick = {
scope.launch { drawerState.close() }
authViewModel.logout()
onLogout()
},
onCloseDrawer = {
scope.launch { drawerState.close() }
},
onRemoveAdsClick = {
if (AdManager.areAdsDisabled()) {
Toast.makeText(
context,
"Ads disabled — ${AdManager.getRemainingMinutes()} minutes remaining",
Toast.LENGTH_SHORT
).show()
} else if (AdManager.isRewardedReady()) {
AdManager.showRewardedAd(
activity = context as Activity,
onSessionActivated = { remaining ->
Toast.makeText(
context,
"
Toast.LENGTH_LONG
).show()
},
onNotReady = {
Toast.makeText(
context,
"Ad not ready, try again",
Toast.LENGTH_SHORT
).show()
AdManager.loadRewardedAd(context)
}
)
} else {
Toast.makeText(context, "Loading ad, try again", Toast.LENGTH_SHORT)
.show()
AdManager.loadRewardedAd(context)
}
}
)
}
}
) {
NavHost(
navController = navController,
startDestination = Routes.HomeScreen.route
) {
composable(route = Routes.HomeScreen.route) {
HomeScreen(
stateHomeScreen = state,
event = homeScreenViewModel::onEvent,
navController = navController,
onMenuClick = {
Log.d("DRAWER", "onClick — isDrawerOpening=$isDrawerOpening, currentValue=${drawerState.currentValue}, isAnimating=${drawerState.isAnimationRunning}")
if (!isDrawerOpening && !drawerState.isOpen && !drawerState.isAnimationRunning) {
isDrawerOpening = true
scope.launch {
try {
drawerState.open()
} catch (e: Exception) {
Log.e("DRAWER", "Error: ${e.message}", e)
} finally {
isDrawerOpening = false
}
}
} else {
Log.d("DRAWER", "IGNORED")
}
},
networkStatus = networkStatus,
snackbarHostState = snackbarHostState
)
}
composable(route = Routes.SettingsScreen.route) {
SettingsScreen(
onBackClick = { navController.popBackStack() },
onPrivacyPolicyClick = { navController.navigate(Routes.PrivacyPolicyScreen.route) },
onTermsClick = { navController.navigate(Routes.TermsAndConditionsScreen.route) }
)
}
// Other Composable
}
}
Экран настроек
@SuppressLint("LocalContextGetResourceValueCall")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
onBackClick: () -> Unit,
onPrivacyPolicyClick: () -> Unit,
onTermsClick: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel()
) {
val context = LocalContext.current
val activity = context as? Activity
val selectedMode by viewModel.selectedMode.collectAsState()
var showThemeDialog by remember { mutableStateOf(false) }
var showAboutDialog by remember { mutableStateOf(false) }
var showRationaleDialog by remember { mutableStateOf(false) }
var showSettingsDialog by remember { mutableStateOf(false) }
val notificationLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
Toast.makeText(
context,
context.getString(R.string.notification_permission_granted),
Toast.LENGTH_SHORT
).show()
context.goToNotificationSettings()
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val shouldShow = activity?.shouldShowRequestPermissionRationale(
Manifest.permission.POST_NOTIFICATIONS
) ?: false
if (shouldShow) {
showRationaleDialog = true
} else {
showSettingsDialog = true
}
}
}
}
// ── Dialogs ──────────────────────────────────────────────────────────
if (showThemeDialog) {
ThemeSelectionDialog(
currentMode = selectedMode,
onDismiss = { showThemeDialog = false },
onModeSelected = { mode ->
Log.d("THEME", "saving mode = $mode")
viewModel.updateMode(mode)
showThemeDialog = false
}
)
}
// ── UI ───────────────────────────────────────────────────────────────
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
// AppBar
TopAppBar(
title = {
Text(
text = stringResource(R.string.settings),
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimary
)
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
painter = painterResource(R.drawable.baseline_arrow_back_24),
contentDescription = "Back",
tint = MaterialTheme.colorScheme.onPrimary
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary
)
)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp)
) {
val items = buildSettingsItems(
onDarkModeClick = {
showThemeDialog = true
},
onNotificationClick = {
if (Build.VERSION.SDK_INT >= 33) {
notificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
context.goToNotificationSettings()
}
},
onPrivacyPolicyClick = onPrivacyPolicyClick,
onTermsClick = onTermsClick,
onRateClick = { context.goToPlayStore() },
onMoreAppsClick = { context.goToMoreApps() },
// في buildSettingsItems — عدّل onRemoveAdsClick
onRemoveAdsClick = {
if (AdManager.areAdsDisabled()) {
Toast.makeText(
context,
"Ads disabled - ${AdManager.getRemainingMinutes()} minutes remaining",
Toast.LENGTH_SHORT
).show()
} else if (AdManager.isRewardedReady()) {
AdManager.showRewardedAd(
activity = context as Activity,
onSessionActivated = { remaining ->
Toast.makeText(
context,
"
Toast.LENGTH_LONG
).show()
},
onNotReady = {
Toast.makeText(
context,
"Ad not ready, try again",
Toast.LENGTH_SHORT
).show()
AdManager.loadRewardedAd(context)
}
)
} else {
Toast.makeText(context, "Loading ad, try again", Toast.LENGTH_SHORT).show()
AdManager.loadRewardedAd(context)
}
},
onAboutClick = { showAboutDialog = true }
)
items.forEachIndexed { index, item ->
SettingsItemRow(item = item)
if (index < items.lastIndex) {
HorizontalDivider(
color = MaterialTheme.colorScheme.outlineVariant,
thickness = 0.8.dp
)
}
}
}
}
}
Полная информация о создании метода
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val selectedMode by settingsViewModel.selectedMode.collectAsState()
val isSystemDark = isSystemInDarkTheme()
val isDark = when (selectedMode) {
AppCompatDelegate.MODE_NIGHT_YES -> true
AppCompatDelegate.MODE_NIGHT_NO -> false
else -> isSystemDark
}
val networkStatus by networkObserver.observe().collectAsState(
initial = if (networkObserver.isConnectedNow())
NetworkStatus.Available else NetworkStatus.Unavailable
)
QuizzyTheme(darkTheme = isDark) {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
inAppUpdateViewModel.events.collect { event ->
when (event) {
is InAppUpdateUiEvent.StartUpdateFlow -> {
startUpdateFlow(event.updateType)
}
InAppUpdateUiEvent.CompleteFlexibleUpdate -> {
inAppUpdateViewModel.completeFlexibleUpdate()
}
}
}
}
val uiState by inAppUpdateViewModel.uiState.collectAsState()
LaunchedEffect(uiState.isFlexibleUpdateDownloaded) {
if (uiState.isFlexibleUpdateDownloaded) {
val result = snackbarHostState.showSnackbar(
message = "Update downloaded",
actionLabel = "Restart",
duration = SnackbarDuration.Indefinite
)
if (result == SnackbarResult.ActionPerformed) {
inAppUpdateViewModel.completeFlexibleUpdate()
}
}
}
Scaffold(
snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
snackbarData = data,
// containerColor = MaterialTheme.colorScheme.primary,
// contentColor = Color.White
)
}
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding()
.padding(paddingValues)
) {
NetworkBanner(
networkStatus = networkStatus,
snackbarHostState = snackbarHostState
)
var currentUser by remember {
mutableStateOf(firebaseAuth.currentUser)
}
AnimatedContent(
targetState = currentUser != null,
transitionSpec = {
if (targetState) {
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it } + fadeOut())
} else {
(slideInHorizontally { -it } + fadeIn()) togetherWith
(slideOutHorizontally { it } + fadeOut())
}
},
label = "auth_transition"
) { isLoggedIn ->
if (isLoggedIn) {
MainAppNavGraph(
currentUser = firebaseAuth.currentUser,
onLogout = {
firebaseAuth.signOut()
currentUser = null
},
networkStatus = networkStatus,
snackbarHostState = snackbarHostState
)
} else {
AuthNavGraph(
onAuthSuccess = {
currentUser = firebaseAuth.currentUser
}
)
}
}
}
}
}
}
}
Мобильная версия