From 24c1b7aac31c2dc32dc83a145661460d199412d9 Mon Sep 17 00:00:00 2001 From: levy Date: Sat, 31 Jan 2026 15:59:55 +0300 Subject: [PATCH 1/5] show onboarding guides --- .../org/cis_india/wsreader/MainActivity.kt | 28 ++- .../wsreader/helpers/Preferencesutils.kt | 2 + .../wsreader/ui/navigation/NavGraph.kt | 3 +- .../ui/screens/home/composables/HomeScreen.kt | 159 +++++++++++++----- .../wsreader/ui/screens/main/MainScreen.kt | 70 +++++--- .../settings/viewmodels/SettingsViewModel.kt | 14 ++ app/src/main/res/values/strings.xml | 11 ++ 7 files changed, 218 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt index ce80162..733f132 100644 --- a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt +++ b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt @@ -23,8 +23,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.ViewModelProvider @@ -35,6 +38,8 @@ import org.cis_india.wsreader.ui.theme.AdjustEdgeToEdge import org.cis_india.wsreader.ui.theme.WikisourceReaderTheme import dagger.hilt.android.AndroidEntryPoint import androidx.navigation.NavController +import com.psoffritti.taptargetcompose.TapTargetCoordinator +import kotlinx.coroutines.delay @AndroidEntryPoint @@ -71,15 +76,28 @@ class MainActivity : AppCompatActivity() { color = MaterialTheme.colorScheme.background ) { val startDestination by mainViewModel.startDestination + val showTapTargets = remember { mutableStateOf(false) } + val status by networkObserver.observe().collectAsState( initial = NetworkObserver.Status.Unavailable ) - MainScreen( - intent = intent, - startDestination = startDestination, - networkStatus = status - ) + LaunchedEffect(key1 = settingsViewModel.showOnboardingTapTargets.value) { + delay(500) // Delay to prevent flickering + showTapTargets.value = settingsViewModel.showOnboardingTapTargets.value + } + + TapTargetCoordinator( + showTapTargets = showTapTargets.value, + onComplete = {settingsViewModel.onboardingComplete()} + ) { + + MainScreen( + intent = intent, + startDestination = startDestination, + networkStatus = status + ) + } } } } diff --git a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt index 899fe74..f570fd3 100644 --- a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt +++ b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt @@ -39,6 +39,8 @@ class PreferenceUtil(context: Context) { // Temporary preference keys const val LIBRARY_ONBOARDING_BOOL = "show_library_onboarding" + const val ONBOARDING_BOOL = "show_onboarding" + const val LIBRARY_SWIPE_TOOLTIP_BOOL = "show_library_tooltip" } diff --git a/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt b/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt index 1b4fc4d..cd86e36 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt @@ -25,6 +25,7 @@ import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument +import com.psoffritti.taptargetcompose.TapTargetScope import org.cis_india.wsreader.helpers.NetworkObserver import org.cis_india.wsreader.ui.screens.categories.composables.CategoriesScreen import org.cis_india.wsreader.ui.screens.categories.composables.CategoryDetailScreen @@ -38,7 +39,7 @@ import org.cis_india.wsreader.ui.screens.welcome.composables.WelcomeScreen @Composable -fun NavGraph( +fun TapTargetScope.NavGraph( startDestination: String, navController: NavHostController, networkStatus: NetworkObserver.Status, diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt index 0c117c6..9417b91 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt @@ -74,6 +74,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -82,6 +83,10 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.psoffritti.taptargetcompose.TapTargetCoordinator +import com.psoffritti.taptargetcompose.TapTargetScope +import com.psoffritti.taptargetcompose.TapTargetStyle +import com.psoffritti.taptargetcompose.TextDefinition import org.cis_india.wsreader.R import org.cis_india.wsreader.helpers.NetworkObserver import org.cis_india.wsreader.helpers.book.BookLanguage @@ -106,7 +111,7 @@ import java.util.Locale @Composable -fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Status) { +fun TapTargetScope.HomeScreen(navController: NavController, networkStatus: NetworkObserver.Status) { val viewModel: HomeViewModel = hiltViewModel() @@ -170,7 +175,7 @@ fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Stat @Composable -private fun HomeScreenScaffold( +private fun TapTargetScope.HomeScreenScaffold( viewModel: HomeViewModel, networkStatus: NetworkObserver.Status, navController: NavController, @@ -429,53 +434,121 @@ private fun SearchBookList(searchBarState: SearchBarState, navController: NavCon } @Composable -private fun HomeTopAppBar( +private fun TapTargetScope.HomeTopAppBar( bookLanguage: BookLanguage, onSearchIconClicked: () -> Unit, onLanguageIconClicked: () -> Unit, onSortIconClicked: () -> Unit ) { - Row( - modifier = Modifier - .fillMaxWidth() - .windowInsetsPadding(WindowInsets.statusBars), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = if (bookLanguage == BookLanguage.AllBooks) - stringResource(id = R.string.home_header) else Locale(bookLanguage.isoCode).getDisplayLanguage(Locale.getDefault()), - fontSize = 28.sp, - color = MaterialTheme.colorScheme.onBackground, - fontFamily = pacificoFont - ) - Spacer(modifier = Modifier.weight(1f)) - IconButton(onClick = onLanguageIconClicked) { - Icon( - imageVector = Icons.Filled.Translate, - contentDescription = stringResource(id = R.string.home_language_icon_desc), - tint = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.size(30.dp) - ) - } - IconButton(onClick = onSortIconClicked) { - Icon( - imageVector = Icons.Filled.Sort, - contentDescription = stringResource(id = R.string.home_sort_icon_desc), - tint = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.size(30.dp) - ) - } - IconButton(onClick = onSearchIconClicked) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), - contentDescription = stringResource(id = R.string.home_search_icon_desc), - tint = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.size(30.dp) + + Row( + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.statusBars), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = if (bookLanguage == BookLanguage.AllBooks) + stringResource(id = R.string.home_header) else Locale(bookLanguage.isoCode).getDisplayLanguage(Locale.getDefault()), + fontSize = 28.sp, + color = MaterialTheme.colorScheme.onBackground, + fontFamily = pacificoFont ) + Spacer(modifier = Modifier.weight(1f)) + IconButton( + onClick = onLanguageIconClicked, + modifier = Modifier.tapTarget( + precedence = 0, + title = TextDefinition( + text = stringResource(R.string.language_guide_title), + textStyle = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + description = TextDefinition( + text = stringResource(R.string.language_guide_description), + textStyle = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + tapTargetStyle = TapTargetStyle( + backgroundColor = MaterialTheme.colorScheme.secondaryContainer, + tapTargetHighlightColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundAlpha = 1f, + ), + ), + ) { + Icon( + imageVector = Icons.Filled.Translate, + contentDescription = stringResource(id = R.string.home_language_icon_desc), + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.size(30.dp) + ) + } + + + IconButton( + onClick = onSortIconClicked, + modifier = Modifier.tapTarget( + precedence = 1, + title = TextDefinition( + text = stringResource(R.string.sorting_guide_title), + textStyle = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + description = TextDefinition( + text = stringResource(R.string.sorting_guide_description), + textStyle = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + tapTargetStyle = TapTargetStyle( + backgroundColor = MaterialTheme.colorScheme.secondaryContainer, + tapTargetHighlightColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundAlpha = 1f, + ), + ), + ) { + Icon( + imageVector = Icons.Filled.Sort, + contentDescription = stringResource(id = R.string.home_sort_icon_desc), + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.size(30.dp) + ) + } + + + IconButton( + onClick = onSearchIconClicked, + modifier = Modifier.tapTarget( + precedence = 2, + title = TextDefinition( + text = stringResource(R.string.search_guide_title), + textStyle = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + description = TextDefinition( + text = stringResource(R.string.search_guide_description), + textStyle = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + tapTargetStyle = TapTargetStyle( + backgroundColor = MaterialTheme.colorScheme.secondaryContainer, + tapTargetHighlightColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundAlpha = 1f, + ), + ), + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_search), + contentDescription = stringResource(id = R.string.home_search_icon_desc), + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.size(30.dp) + ) + } } } -} @Composable @@ -552,5 +625,7 @@ private fun SearchAppBar( @Composable @Preview(showBackground = true) fun HomeScreenPreview() { - HomeScreen(navController = rememberNavController(), NetworkObserver.Status.Unavailable) + TapTargetCoordinator(showTapTargets = true, onComplete = {}) { + HomeScreen(navController = rememberNavController(), NetworkObserver.Status.Unavailable) + } } \ No newline at end of file diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt index a662ebf..a64eb4a 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController @@ -56,7 +57,11 @@ import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.psoffritti.taptargetcompose.TapTargetScope +import com.psoffritti.taptargetcompose.TapTargetStyle +import com.psoffritti.taptargetcompose.TextDefinition import org.cis_india.wsreader.MainViewModel +import org.cis_india.wsreader.R import org.cis_india.wsreader.helpers.NetworkObserver import org.cis_india.wsreader.ui.navigation.BottomBarScreen import org.cis_india.wsreader.ui.navigation.NavGraph @@ -70,7 +75,7 @@ val bottomNavPadding = 70.dp @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable -fun MainScreen( +fun TapTargetScope.MainScreen( intent: Intent, startDestination: String, networkStatus: NetworkObserver.Status, @@ -98,7 +103,7 @@ fun MainScreen( } @Composable -private fun BottomBar(navController: NavHostController) { +private fun TapTargetScope.BottomBar(navController: NavHostController) { val screens = listOf( BottomBarScreen.Home, BottomBarScreen.Categories, @@ -115,33 +120,37 @@ private fun BottomBar(navController: NavHostController) { enter = slideInVertically(initialOffsetY = { it }), exit = slideOutVertically(targetOffsetY = { it }), content = { - Row( - modifier = Modifier - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)) - .padding(12.dp) - .fillMaxWidth() - .windowInsetsPadding(WindowInsets.navigationBars), - horizontalArrangement = Arrangement.SpaceAround, - verticalAlignment = Alignment.CenterVertically, - ) { - screens.forEach { screen -> - CustomBottomNavigationItem( - screen = screen, isSelected = screen.route == currentDestination?.route - ) { - navController.navigate(screen.route) { - popUpTo(navController.graph.findStartDestination().id) - launchSingleTop = true + + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)) + .padding(12.dp) + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.navigationBars), + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically, + ) { + screens.forEachIndexed { index, screen -> + CustomBottomNavigationItem( + screen = screen, + isSelected = screen.route == currentDestination?.route, + index = index+3 // used in tab target order. + ) { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) + launchSingleTop = true + } } } } - } }) } @Composable -private fun CustomBottomNavigationItem( +private fun TapTargetScope.CustomBottomNavigationItem( screen: BottomBarScreen, isSelected: Boolean, + index: Int, onClick: () -> Unit, ) { val background = @@ -156,7 +165,25 @@ private fun CustomBottomNavigationItem( .clickable(onClick = onClick) ) { Row( - modifier = Modifier.padding(12.dp), + modifier = Modifier.tapTarget( + precedence = index, + title = TextDefinition( + text = stringResource(id = R.string.tap_target_screen_title, stringResource(id = screen.title)), + textStyle = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + description = TextDefinition( + text = stringResource(id = R.string.tap_target_screen_description, stringResource(id = screen.title)), + textStyle = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ), + tapTargetStyle = TapTargetStyle( + backgroundColor = MaterialTheme.colorScheme.secondaryContainer, + tapTargetHighlightColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundAlpha = 1f, + ), + ).padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { @@ -180,6 +207,7 @@ private fun CustomBottomNavigationItem( } } + @Composable private fun HandleShortcutIntent(intent: Intent, navController: NavController) { val data = intent.data diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt index deda2d4..21e34ae 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt @@ -19,6 +19,9 @@ package org.cis_india.wsreader.ui.screens.settings.viewmodels import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -43,6 +46,12 @@ class SettingsViewModel @Inject constructor( val amoledTheme: LiveData = _amoledTheme val materialYou: LiveData = _materialYou + private val _showOnboardingTapTargets: MutableState = mutableStateOf( + value = preferenceUtil.getBoolean(PreferenceUtil.ONBOARDING_BOOL, true) + ) + + val showOnboardingTapTargets: State = _showOnboardingTapTargets + init { _theme.value = ThemeMode.entries.toTypedArray()[getThemeValue()] _amoledTheme.value = getAmoledThemeValue() @@ -87,6 +96,11 @@ class SettingsViewModel @Inject constructor( PreferenceUtil.INTERNAL_READER_BOOL, true ) + fun onboardingComplete() { + preferenceUtil.putBoolean(PreferenceUtil.ONBOARDING_BOOL, false) + _showOnboardingTapTargets.value = false + } + @Composable fun getCurrentTheme(): ThemeMode { return if (theme.value == ThemeMode.Auto) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b89e2e..5b29ba9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,6 +32,17 @@ Browse by language Sort by + + Switch Language + Filter Books By Language + Sort Books + Tap to sort Books + Search function + Tap to search a book + + %1$s screen + Navigate to %1$s screen + Categories No books available in %s language for this category. From 0bf3a46f16ae2892d772dadd024970b538f0139e Mon Sep 17 00:00:00 2001 From: levy Date: Mon, 2 Feb 2026 22:01:41 +0300 Subject: [PATCH 2/5] remove tap target from global component and handle it in different screens --- .../org/cis_india/wsreader/MainActivity.kt | 18 --------- .../wsreader/helpers/Preferencesutils.kt | 3 +- .../wsreader/ui/navigation/NavGraph.kt | 7 ++-- .../ui/screens/home/composables/HomeScreen.kt | 37 ++++++++++++++----- .../wsreader/ui/screens/main/MainScreen.kt | 32 ++++++++++++++-- .../settings/viewmodels/SettingsViewModel.kt | 29 ++++++++++++--- 6 files changed, 85 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt index 733f132..ab72ca5 100644 --- a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt +++ b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt @@ -23,11 +23,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.ViewModelProvider @@ -38,8 +35,6 @@ import org.cis_india.wsreader.ui.theme.AdjustEdgeToEdge import org.cis_india.wsreader.ui.theme.WikisourceReaderTheme import dagger.hilt.android.AndroidEntryPoint import androidx.navigation.NavController -import com.psoffritti.taptargetcompose.TapTargetCoordinator -import kotlinx.coroutines.delay @AndroidEntryPoint @@ -76,28 +71,15 @@ class MainActivity : AppCompatActivity() { color = MaterialTheme.colorScheme.background ) { val startDestination by mainViewModel.startDestination - val showTapTargets = remember { mutableStateOf(false) } - val status by networkObserver.observe().collectAsState( initial = NetworkObserver.Status.Unavailable ) - LaunchedEffect(key1 = settingsViewModel.showOnboardingTapTargets.value) { - delay(500) // Delay to prevent flickering - showTapTargets.value = settingsViewModel.showOnboardingTapTargets.value - } - - TapTargetCoordinator( - showTapTargets = showTapTargets.value, - onComplete = {settingsViewModel.onboardingComplete()} - ) { - MainScreen( intent = intent, startDestination = startDestination, networkStatus = status ) - } } } } diff --git a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt index f570fd3..2e7d774 100644 --- a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt +++ b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt @@ -39,7 +39,8 @@ class PreferenceUtil(context: Context) { // Temporary preference keys const val LIBRARY_ONBOARDING_BOOL = "show_library_onboarding" - const val ONBOARDING_BOOL = "show_onboarding" + const val HOME_ONBOARDING_BOOL = "show_home_onboarding" + const val NAV_ONBOARDING_BOOL = "show_nav_onboarding" const val LIBRARY_SWIPE_TOOLTIP_BOOL = "show_library_tooltip" } diff --git a/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt b/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt index cd86e36..164dcde 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/navigation/NavGraph.kt @@ -25,7 +25,6 @@ import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.psoffritti.taptargetcompose.TapTargetScope import org.cis_india.wsreader.helpers.NetworkObserver import org.cis_india.wsreader.ui.screens.categories.composables.CategoriesScreen import org.cis_india.wsreader.ui.screens.categories.composables.CategoryDetailScreen @@ -35,14 +34,16 @@ import org.cis_india.wsreader.ui.screens.library.composables.LibraryScreen import org.cis_india.wsreader.ui.screens.settings.composables.AboutScreen import org.cis_india.wsreader.ui.screens.settings.composables.OSLScreen import org.cis_india.wsreader.ui.screens.settings.composables.SettingsScreen +import org.cis_india.wsreader.ui.screens.settings.viewmodels.SettingsViewModel import org.cis_india.wsreader.ui.screens.welcome.composables.WelcomeScreen @Composable -fun TapTargetScope.NavGraph( +fun NavGraph( startDestination: String, navController: NavHostController, networkStatus: NetworkObserver.Status, + settingsViewModel: SettingsViewModel ) { NavHost( navController = navController, @@ -73,7 +74,7 @@ fun TapTargetScope.NavGraph( } else bottomNavPopEnter() }, popExitTransition = { bottomNavPopExit() }) { - HomeScreen(navController, networkStatus) + HomeScreen(navController, networkStatus, settingsViewModel) } /** Book Detail Screen */ diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt index 9417b91..d8586cc 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/home/composables/HomeScreen.kt @@ -107,11 +107,12 @@ import org.cis_india.wsreader.ui.screens.main.bottomNavPadding import org.cis_india.wsreader.ui.theme.pacificoFont import org.cis_india.wsreader.ui.theme.poppinsFont import kotlinx.coroutines.delay +import org.cis_india.wsreader.ui.screens.settings.viewmodels.SettingsViewModel import java.util.Locale @Composable -fun TapTargetScope.HomeScreen(navController: NavController, networkStatus: NetworkObserver.Status) { +fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Status, settingsViewModel: SettingsViewModel = hiltViewModel()) { val viewModel: HomeViewModel = hiltViewModel() @@ -147,6 +148,18 @@ fun TapTargetScope.HomeScreen(navController: NavController, networkStatus: Netwo } } + val showHomeTapTargets = remember { mutableStateOf(false) } + + + LaunchedEffect(settingsViewModel.showHomeOnboardingTapTargets.value) { + delay(500) // Delay to prevent flickering + if(settingsViewModel.showHomeOnboardingTapTargets.value) { + showHomeTapTargets.value = true + }else{ + showHomeTapTargets.value = false + } + } + val showLanguageSheet = remember { mutableStateOf(false) } BookLanguageSheet( showBookLanguage = showLanguageSheet, @@ -161,14 +174,20 @@ fun TapTargetScope.HomeScreen(navController: NavController, networkStatus: Netwo onSortOptionChange = { viewModel.onAction(UserAction.SortOptionClicked(it)) } ) - HomeScreenScaffold( - viewModel = viewModel, - networkStatus = networkStatus, - navController = navController, - sysBackButtonState = sysBackButtonState, - showLanguageSheet = showLanguageSheet, - showSortSheet = showSortSheet - ) + TapTargetCoordinator( + showTapTargets = showHomeTapTargets.value, + onComplete = { settingsViewModel.homeOnboardingComplete() } + ) { + + HomeScreenScaffold( + viewModel = viewModel, + networkStatus = networkStatus, + navController = navController, + sysBackButtonState = sysBackButtonState, + showLanguageSheet = showLanguageSheet, + showSortSheet = showSortSheet + ) + } } diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt index a64eb4a..6dc3b46 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt @@ -52,11 +52,13 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.psoffritti.taptargetcompose.TapTargetCoordinator import com.psoffritti.taptargetcompose.TapTargetScope import com.psoffritti.taptargetcompose.TapTargetStyle import com.psoffritti.taptargetcompose.TextDefinition @@ -66,6 +68,7 @@ import org.cis_india.wsreader.helpers.NetworkObserver import org.cis_india.wsreader.ui.navigation.BottomBarScreen import org.cis_india.wsreader.ui.navigation.NavGraph import org.cis_india.wsreader.ui.navigation.Screens +import org.cis_india.wsreader.ui.screens.settings.viewmodels.SettingsViewModel import org.cis_india.wsreader.ui.theme.poppinsFont /** @@ -75,21 +78,42 @@ val bottomNavPadding = 70.dp @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable -fun TapTargetScope.MainScreen( +fun MainScreen( intent: Intent, startDestination: String, networkStatus: NetworkObserver.Status, ) { + val settingsViewModel: SettingsViewModel = hiltViewModel() + val navController = rememberNavController() + + + val showNavTapTargets = remember { mutableStateOf(false) } + + + LaunchedEffect(settingsViewModel.showNavOnboardingTapTargets.value) { + if(settingsViewModel.showNavOnboardingTapTargets.value) { + showNavTapTargets.value = true + }else{ + showNavTapTargets.value = false + } + } + Scaffold( bottomBar = { - BottomBar(navController = navController) + TapTargetCoordinator( + showTapTargets = showNavTapTargets.value, + onComplete = {settingsViewModel.navOnboardingComplete()} + ) { + BottomBar(navController = navController) + } }, containerColor = MaterialTheme.colorScheme.background ) { NavGraph( startDestination = startDestination, navController = navController, - networkStatus = networkStatus + networkStatus = networkStatus, + settingsViewModel= settingsViewModel ) val shouldHandleShortCut = remember { mutableStateOf(false) } @@ -134,7 +158,7 @@ private fun TapTargetScope.BottomBar(navController: NavHostController) { CustomBottomNavigationItem( screen = screen, isSelected = screen.route == currentDestination?.route, - index = index+3 // used in tab target order. + index = index // used in tab target order. ) { navController.navigate(screen.route) { popUpTo(navController.graph.findStartDestination().id) diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt index 21e34ae..f478e56 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt @@ -46,11 +46,18 @@ class SettingsViewModel @Inject constructor( val amoledTheme: LiveData = _amoledTheme val materialYou: LiveData = _materialYou - private val _showOnboardingTapTargets: MutableState = mutableStateOf( - value = preferenceUtil.getBoolean(PreferenceUtil.ONBOARDING_BOOL, true) + private val _showHomeOnboardingTapTargets: MutableState = mutableStateOf( + value = preferenceUtil.getBoolean(PreferenceUtil.HOME_ONBOARDING_BOOL, true) ) - val showOnboardingTapTargets: State = _showOnboardingTapTargets + val showHomeOnboardingTapTargets: State = _showHomeOnboardingTapTargets + + private val _showNavOnboardingTapTargets: MutableState = mutableStateOf( + value = preferenceUtil.getBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, false) + ) + + val showNavOnboardingTapTargets: State = _showNavOnboardingTapTargets + init { _theme.value = ThemeMode.entries.toTypedArray()[getThemeValue()] @@ -96,9 +103,19 @@ class SettingsViewModel @Inject constructor( PreferenceUtil.INTERNAL_READER_BOOL, true ) - fun onboardingComplete() { - preferenceUtil.putBoolean(PreferenceUtil.ONBOARDING_BOOL, false) - _showOnboardingTapTargets.value = false + fun homeOnboardingComplete() { + preferenceUtil.putBoolean(PreferenceUtil.HOME_ONBOARDING_BOOL, false) + preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, true) + + _showHomeOnboardingTapTargets.value = false + _showNavOnboardingTapTargets.value = true + + } + + + fun navOnboardingComplete() { + preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, false) + _showNavOnboardingTapTargets.value = false } @Composable From 3c6cc202103e1045b64ba6f071e7722593bf2478 Mon Sep 17 00:00:00 2001 From: levy Date: Tue, 3 Feb 2026 06:13:00 +0300 Subject: [PATCH 3/5] added switch item in settings page to trigger home screen tap target --- .../wsreader/helpers/Preferencesutils.kt | 2 ++ .../settings/composables/SettingsScreen.kt | 18 ++++++++++ .../settings/viewmodels/SettingsViewModel.kt | 36 +++++++++++++++++-- app/src/main/res/values/strings.xml | 3 ++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt index 2e7d774..ef63f18 100644 --- a/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt +++ b/app/src/main/java/org/cis_india/wsreader/helpers/Preferencesutils.kt @@ -42,6 +42,8 @@ class PreferenceUtil(context: Context) { const val HOME_ONBOARDING_BOOL = "show_home_onboarding" const val NAV_ONBOARDING_BOOL = "show_nav_onboarding" + const val FIRST_ONBOARDING_BOOL = "first_onboarding" + const val LIBRARY_SWIPE_TOOLTIP_BOOL = "show_library_tooltip" } diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/composables/SettingsScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/composables/SettingsScreen.kt index 5272e40..e8e8a78 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/composables/SettingsScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/composables/SettingsScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BrightnessMedium import androidx.compose.material.icons.filled.Contrast +import androidx.compose.material.icons.filled.Help import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.LocalPolice @@ -204,6 +205,13 @@ private fun GeneralOptionsUI( val context = LocalContext.current val coroutineScope = rememberCoroutineScope() + val guideSwitch = remember { mutableStateOf(viewModel.getOnboardingGuideValue()) } + val guideDesc = if (guideSwitch.value) { + stringResource(id = R.string.onboarding_guide_inactive) + } else { + stringResource(id = R.string.onboarding_guide_active) + } + Column( modifier = Modifier .padding(horizontal = 14.dp) @@ -241,6 +249,16 @@ private fun GeneralOptionsUI( } ) + SettingItemWIthSwitch( + icon = Icons.Filled.Help, + mainText = stringResource(id = R.string.toggle_guide_title), + subText = guideDesc, + switchState = guideSwitch, + onCheckChange = { + viewModel.setOnboardingGuide(it) + guideSwitch.value = it + }) + } } diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt index f478e56..9d023da 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/settings/viewmodels/SettingsViewModel.kt @@ -58,6 +58,12 @@ class SettingsViewModel @Inject constructor( val showNavOnboardingTapTargets: State = _showNavOnboardingTapTargets + private val _firstOnboardingTapTargets: MutableState = mutableStateOf( + value = preferenceUtil.getBoolean(PreferenceUtil.FIRST_ONBOARDING_BOOL, true) + ) + + val firstOnboardingTapTargets: State = _firstOnboardingTapTargets + init { _theme.value = ThemeMode.entries.toTypedArray()[getThemeValue()] @@ -76,6 +82,22 @@ class SettingsViewModel @Inject constructor( preferenceUtil.putBoolean(PreferenceUtil.AMOLED_THEME_BOOL, newValue) } + fun setOnboardingGuide(newValue: Boolean) { + preferenceUtil.putBoolean(PreferenceUtil.HOME_ONBOARDING_BOOL, newValue) + + if (newValue) { + _showHomeOnboardingTapTargets.value = true + _showNavOnboardingTapTargets.value = false + preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, false) + } else { + preferenceUtil.putBoolean(PreferenceUtil.HOME_ONBOARDING_BOOL, newValue) + preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, false) + + _showHomeOnboardingTapTargets.value = false + _showNavOnboardingTapTargets.value = false + } + } + fun setMaterialYou(newValue: Boolean) { _materialYou.postValue(newValue) preferenceUtil.putBoolean(PreferenceUtil.MATERIAL_YOU_BOOL, newValue) @@ -95,6 +117,10 @@ class SettingsViewModel @Inject constructor( PreferenceUtil.AMOLED_THEME_BOOL, false ) + fun getOnboardingGuideValue() = preferenceUtil.getBoolean( + PreferenceUtil.HOME_ONBOARDING_BOOL, false + ) + fun getMaterialYouValue() = preferenceUtil.getBoolean( PreferenceUtil.MATERIAL_YOU_BOOL, Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ) @@ -105,10 +131,14 @@ class SettingsViewModel @Inject constructor( fun homeOnboardingComplete() { preferenceUtil.putBoolean(PreferenceUtil.HOME_ONBOARDING_BOOL, false) - preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, true) - _showHomeOnboardingTapTargets.value = false - _showNavOnboardingTapTargets.value = true + + if(firstOnboardingTapTargets.value) { + preferenceUtil.putBoolean(PreferenceUtil.NAV_ONBOARDING_BOOL, true) + preferenceUtil.putBoolean(PreferenceUtil.FIRST_ONBOARDING_BOOL, false) + _showNavOnboardingTapTargets.value = true + _firstOnboardingTapTargets.value = false + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b29ba9..e6f009f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,6 +135,9 @@ Open source licenses. App Information Show App information & useful links + Show Onboarding Guide + Hide Onboarding Guide + Toggle Onboarding Guide Book cover image From fda2466af44018469245f8a3da617e7e66ff2c47 Mon Sep 17 00:00:00 2001 From: levy Date: Tue, 3 Feb 2026 18:15:55 +0300 Subject: [PATCH 4/5] added navigation tap target title and descriptions as separate for varrying content --- .../wsreader/ui/navigation/BottomBarScreen.kt | 20 ++++++++++++++----- .../wsreader/ui/screens/main/MainScreen.kt | 4 ++-- app/src/main/res/values/strings.xml | 12 +++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/cis_india/wsreader/ui/navigation/BottomBarScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/navigation/BottomBarScreen.kt index 21ddce7..46d7897 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/navigation/BottomBarScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/navigation/BottomBarScreen.kt @@ -21,29 +21,39 @@ import org.cis_india.wsreader.R sealed class BottomBarScreen( val route: String, val title: Int, - val icon: Int + val icon: Int, + val tap_target_coodinator_title: Int, + val tap_target_coodinator_description: Int, ) { data object Home : BottomBarScreen( route = "home", title = R.string.navigation_home, - icon = R.drawable.ic_nav_home + icon = R.drawable.ic_nav_home, + tap_target_coodinator_title = R.string.home_tap_target_coodinator_title, + tap_target_coodinator_description = R.string.home_tap_target_coodinator_description ) data object Categories : BottomBarScreen( route = "categories", title = R.string.navigation_categories, - icon = R.drawable.ic_nav_categories + icon = R.drawable.ic_nav_categories, + tap_target_coodinator_title = R.string.category_tap_target_coodinator_title, + tap_target_coodinator_description = R.string.category_tap_target_coodinator_description ) data object Library : BottomBarScreen( route = "library", title = R.string.navigation_library, - icon = R.drawable.ic_nav_library + icon = R.drawable.ic_nav_library, + tap_target_coodinator_title = R.string.library_tap_target_coodinator_title, + tap_target_coodinator_description = R.string.library_tap_target_coodinator_description ) data object Settings : BottomBarScreen( route = "settings", title = R.string.navigation_settings, - icon = R.drawable.ic_nav_settings + icon = R.drawable.ic_nav_settings, + tap_target_coodinator_title = R.string.settings_tap_target_coodinator_title, + tap_target_coodinator_description = R.string.settings_tap_target_coodinator_description ) } \ No newline at end of file diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt index 6dc3b46..8389732 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/main/MainScreen.kt @@ -192,13 +192,13 @@ private fun TapTargetScope.CustomBottomNavigationItem( modifier = Modifier.tapTarget( precedence = index, title = TextDefinition( - text = stringResource(id = R.string.tap_target_screen_title, stringResource(id = screen.title)), + text = stringResource(screen.tap_target_coodinator_title), textStyle = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSecondaryContainer ), description = TextDefinition( - text = stringResource(id = R.string.tap_target_screen_description, stringResource(id = screen.title)), + text = stringResource(screen.tap_target_coodinator_description), textStyle = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSecondaryContainer ), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6f009f..8869dc8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,18 @@ Library Settings + Home Screen + Discover thousands of free, community-curated books and historical texts from the Wikisource library. + + Categories Section + Find exactly what you’re looking for by browsing through categories like plays, novels, and poetry. + + Your Library + Find all your books here. You can manage your collection, track your progress, or share favorites with friends. + + App Settings + Customize your reading experience, and adjust language preferences. + TIP! Swipe library items left or right to share or view book details. From b82d673f67b34d3431b8863e068f9bf116056ae3 Mon Sep 17 00:00:00 2001 From: levy Date: Mon, 9 Feb 2026 12:50:32 +0300 Subject: [PATCH 5/5] revert unnecessary change --- .../main/java/org/cis_india/wsreader/MainActivity.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt index ab72ca5..ce80162 100644 --- a/app/src/main/java/org/cis_india/wsreader/MainActivity.kt +++ b/app/src/main/java/org/cis_india/wsreader/MainActivity.kt @@ -75,11 +75,11 @@ class MainActivity : AppCompatActivity() { initial = NetworkObserver.Status.Unavailable ) - MainScreen( - intent = intent, - startDestination = startDestination, - networkStatus = status - ) + MainScreen( + intent = intent, + startDestination = startDestination, + networkStatus = status + ) } } }