Guided
@ -1,6 +1,7 @@
|
||||
package com.twofasapp.ui.main
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
@ -14,10 +15,14 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.google.accompanist.navigation.material.BottomSheetNavigator
|
||||
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
||||
import com.google.accompanist.navigation.material.bottomSheet
|
||||
import com.twofasapp.android.navigation.Modal
|
||||
import com.twofasapp.android.navigation.NavAnimation
|
||||
import com.twofasapp.android.navigation.NavArg
|
||||
import com.twofasapp.android.navigation.Screen
|
||||
import com.twofasapp.android.navigation.clearGraphBackStack
|
||||
import com.twofasapp.android.navigation.intentFor
|
||||
import com.twofasapp.android.navigation.withArg
|
||||
@ -32,8 +37,12 @@ import com.twofasapp.feature.browserext.notification.BrowserExtGraph
|
||||
import com.twofasapp.feature.browserext.notification.browserExtNavigation
|
||||
import com.twofasapp.feature.externalimport.navigation.ExternalImportGraph
|
||||
import com.twofasapp.feature.externalimport.navigation.externalImportNavigation
|
||||
import com.twofasapp.feature.home.navigation.GuideInitRoute
|
||||
import com.twofasapp.feature.home.navigation.GuidePagerRoute
|
||||
import com.twofasapp.feature.home.navigation.GuidesRoute
|
||||
import com.twofasapp.feature.home.navigation.HomeGraph
|
||||
import com.twofasapp.feature.home.navigation.HomeNavigationListener
|
||||
import com.twofasapp.feature.home.navigation.HomeNode
|
||||
import com.twofasapp.feature.home.navigation.NotificationsGraph
|
||||
import com.twofasapp.feature.home.navigation.homeNavigation
|
||||
import com.twofasapp.feature.home.navigation.notificationsNavigation
|
||||
@ -73,6 +82,12 @@ internal fun MainNavHost(
|
||||
|
||||
var recentlyAddedService by remember { mutableStateOf<RecentlyAddedService?>(null) }
|
||||
|
||||
BackHandler(enabled = bottomSheetNavigator.navigatorSheetState.isVisible) {
|
||||
scope.launch {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = {
|
||||
if (recentlyAddedService != null) {
|
||||
@ -135,11 +150,11 @@ internal fun MainNavHost(
|
||||
|
||||
override fun openAddServiceModal() {
|
||||
recentlyAddedService = null
|
||||
navController.navigate(ModalNavigation.AddService.route)
|
||||
navController.navigate(Modal.AddService.route)
|
||||
}
|
||||
|
||||
override fun openFocusServiceModal(id: Long) {
|
||||
navController.navigate(ModalNavigation.FocusService.route.replace("{id}", id.toString()))
|
||||
navController.navigate(Modal.FocusService.route.replace("{id}", id.toString()))
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -166,13 +181,15 @@ internal fun MainNavHost(
|
||||
browserExtNavigation(navController = navController)
|
||||
securityNavigation(navController = navController)
|
||||
|
||||
bottomSheet(ModalNavigation.AddService.route) {
|
||||
bottomSheet(Modal.AddService.route, listOf(NavArg.AddServiceInitRoute)) {
|
||||
AddServiceModal(
|
||||
onAddedSuccessfully = { recentlyAddedService = it }
|
||||
initRoute = it.arguments?.getString(NavArg.AddServiceInitRoute.name),
|
||||
onAddedSuccessfully = { recentlyAddedService = it },
|
||||
openGuides = { navController.navigate(Screen.Guides.route) }
|
||||
)
|
||||
}
|
||||
|
||||
bottomSheet(ModalNavigation.FocusService.route, listOf(FocusServiceModalNavArg.ServiceId)) {
|
||||
bottomSheet(Modal.FocusService.route, listOf(FocusServiceModalNavArg.ServiceId)) {
|
||||
FocusServiceModal(
|
||||
openService = {
|
||||
navController.navigate(ServiceGraph.route.withArg(ServiceNavArg.ServiceId, it))
|
||||
@ -180,6 +197,41 @@ internal fun MainNavHost(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable(Screen.Guides.route) {
|
||||
GuidesRoute(
|
||||
openGuide = { navController.navigate(Screen.GuideInit.routeWithArgs(NavArg.Guide to it.name)) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(Screen.GuideInit.route, listOf(NavArg.Guide)) {
|
||||
GuideInitRoute(
|
||||
guide = enumValueOf(it.arguments!!.getString(NavArg.Guide.name)!!),
|
||||
openGuide = { guide, guideVariantIndex ->
|
||||
navController.navigate(
|
||||
Screen.GuidePager.routeWithArgs(
|
||||
NavArg.Guide to guide.name,
|
||||
NavArg.GuideVariantIndex to guideVariantIndex,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable(Screen.GuidePager.route, listOf(NavArg.Guide, NavArg.GuideVariantIndex)) {
|
||||
GuidePagerRoute(
|
||||
guide = enumValueOf(it.arguments!!.getString(NavArg.Guide.name)!!),
|
||||
guideVariantIndex = it.arguments!!.getInt(NavArg.GuideVariantIndex.name),
|
||||
openAddScan = {
|
||||
navController.popBackStack(HomeNode.Services.route, false)
|
||||
navController.navigate(Modal.AddService.route)
|
||||
},
|
||||
openAddManually = {
|
||||
navController.popBackStack(HomeNode.Services.route, false)
|
||||
navController.navigate(Modal.AddService.routeWithArgs(NavArg.AddServiceInitRoute to "manual"))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,12 +7,14 @@ import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.accompanist.navigation.material.BottomSheetNavigator
|
||||
@ -25,6 +27,7 @@ import com.twofasapp.designsystem.TwTheme
|
||||
import com.twofasapp.feature.home.navigation.HomeGraph
|
||||
import com.twofasapp.feature.startup.navigation.StartupGraph
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@ -55,6 +58,22 @@ internal fun MainScreen(
|
||||
val bottomSheetNavigator = remember { BottomSheetNavigator(sheetState) }
|
||||
val navController: NavHostController = rememberNavController(bottomSheetNavigator)
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(Unit){
|
||||
navController.addOnDestinationChangedListener { _, destination, arguments ->
|
||||
val argumentsLog: String = if (destination.arguments.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
"args=" + destination.arguments.map {
|
||||
@Suppress("DEPRECATION")
|
||||
"${it.key}=${arguments?.get(it.key)}"
|
||||
}.toString()
|
||||
}
|
||||
|
||||
Timber.tag("NavController").d("route=${destination.route} $argumentsLog")
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.startDestination != null && uiState.selectedTheme != null) {
|
||||
CompositionLocalProvider(
|
||||
LocalAppTheme provides when (uiState.selectedTheme!!) {
|
||||
|
@ -1,7 +0,0 @@
|
||||
package com.twofasapp.ui.main
|
||||
|
||||
sealed class ModalNavigation(val route: String) {
|
||||
|
||||
object FocusService : ModalNavigation("modal/focusservice/{id}")
|
||||
object AddService : ModalNavigation("modal/addservice")
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.twofasapp.android.navigation
|
||||
|
||||
import androidx.navigation.NamedNavArgument
|
||||
|
||||
sealed class Modal(val route: String) {
|
||||
|
||||
fun routeWithArgs(vararg args: Pair<NamedNavArgument, Any>): String {
|
||||
return route.replaceArgsInRoute(*args)
|
||||
}
|
||||
|
||||
data object FocusService : Modal("modal/focusservice/{id}")
|
||||
data object AddService : Modal("modal/addservice?init={${NavArg.AddServiceInitRoute.name}}")
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.twofasapp.android.navigation
|
||||
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
|
||||
object NavArg {
|
||||
val AddServiceInitRoute = navArgument("AddServiceInitRoute") { type = NavType.StringType; nullable = true; defaultValue = null }
|
||||
val Guide = navArgument("Guide") { type = NavType.StringType; }
|
||||
val GuideVariantIndex = navArgument("GuideVariantIndex") { type = NavType.IntType; }
|
||||
}
|
@ -23,4 +23,10 @@ fun NamedNavArgument.withDefault(any: Any?): NamedNavArgument {
|
||||
|
||||
fun <T> SavedStateHandle.getOrThrow(key: String): T {
|
||||
return get<T>(key) ?: throw IllegalNavArgException(key)
|
||||
}
|
||||
|
||||
internal fun String.replaceArgsInRoute(vararg args: Pair<NamedNavArgument, Any>): String {
|
||||
var routeWithArgs = this
|
||||
args.forEach { arg -> routeWithArgs = routeWithArgs.withArg(arg.first, arg.second) }
|
||||
return routeWithArgs
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.twofasapp.android.navigation
|
||||
|
||||
import androidx.navigation.NamedNavArgument
|
||||
|
||||
sealed class Screen(val route: String) {
|
||||
|
||||
fun routeWithArgs(vararg args: Pair<NamedNavArgument, Any>): String {
|
||||
return route.replaceArgsInRoute(*args)
|
||||
}
|
||||
|
||||
data object Guides : Screen("guides")
|
||||
data object GuideInit : Screen("guides/init?guide={${NavArg.Guide.name}}")
|
||||
data object GuidePager : Screen("guides/pager?guide={${NavArg.Guide.name}}&variant={${NavArg.GuideVariantIndex.name}}")
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package com.twofasapp.designsystem.internal
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import com.twofasapp.designsystem.TwTheme
|
||||
|
||||
abstract class ThemeColors {
|
||||
abstract val background: Color
|
||||
@ -44,3 +47,6 @@ abstract class ThemeColors {
|
||||
val LocalThemeColors = staticCompositionLocalOf<ThemeColors> {
|
||||
ThemeColorsLight()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun isDarkTheme() = TwTheme.color.background.luminance() < 0.5
|
||||
|
@ -8,6 +8,7 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.twofasapp.designsystem.R
|
||||
import com.twofasapp.designsystem.TwTheme
|
||||
|
||||
@Immutable
|
||||
@Stable
|
||||
|
@ -140,6 +140,7 @@ fun DsService(
|
||||
ServiceInfo(
|
||||
text = state.info,
|
||||
textStyles = textStyles,
|
||||
style = style,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
|
@ -64,6 +64,7 @@ fun DsServiceModal(
|
||||
ServiceInfo(
|
||||
text = state.info,
|
||||
textStyles = ServiceTextDefaults.modal(),
|
||||
style = ServiceStyle.Default,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
|
@ -49,7 +49,7 @@ fun DsServiceSimple(
|
||||
|
||||
Column(Modifier.weight(1f)) {
|
||||
ServiceName(text = state.name)
|
||||
ServiceInfo(text = state.info)
|
||||
ServiceInfo(text = state.info, spacer = false)
|
||||
}
|
||||
|
||||
content()
|
||||
|
@ -3,7 +3,8 @@ package com.twofasapp.designsystem.service.atoms
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -11,6 +12,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.twofasapp.designsystem.TwTheme
|
||||
import com.twofasapp.designsystem.service.ServiceStyle
|
||||
import com.twofasapp.locale.TwLocale
|
||||
|
||||
@Composable
|
||||
@ -25,7 +27,7 @@ internal fun ServiceName(
|
||||
color = TwTheme.color.onSurfacePrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier,
|
||||
modifier = modifier.padding(bottom = 2.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,6 +35,8 @@ internal fun ServiceName(
|
||||
@Composable
|
||||
internal fun ServiceInfo(
|
||||
text: String?,
|
||||
style: ServiceStyle = ServiceStyle.Default,
|
||||
spacer: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyles: ServiceTextStyle = ServiceTextDefaults.default(),
|
||||
) {
|
||||
@ -45,8 +49,17 @@ internal fun ServiceInfo(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
if (spacer) {
|
||||
Spacer(
|
||||
Modifier.size(
|
||||
when (style) {
|
||||
ServiceStyle.Default -> 12.dp
|
||||
ServiceStyle.Compact -> 2.dp
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +68,6 @@ internal fun ServiceInfo(
|
||||
private fun Preview() {
|
||||
Column {
|
||||
ServiceName(text = TwLocale.strings.placeholder, modifier = Modifier.fillMaxWidth())
|
||||
ServiceInfo(text = TwLocale.strings.placeholderMedium, modifier = Modifier.fillMaxWidth())
|
||||
ServiceInfo(text = TwLocale.strings.placeholderMedium, modifier = Modifier.fillMaxWidth(), style = ServiceStyle.Default)
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 7.5 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_2fas.webp
Normal file
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 11 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_alert.webp
Normal file
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 9.1 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_error.webp
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_eye.webp
Normal file
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 6.0 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_gears.webp
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_github.webp
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
core/designsystem/src/main/res/drawable/illustration_retype.webp
Normal file
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 8.2 KiB |