Merge branch 'release/4.7.0' into main
@ -5,6 +5,7 @@ plugins {
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.kotlinParcelize)
|
||||
alias(libs.plugins.kotlinKapt)
|
||||
alias(libs.plugins.ksp)
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
}
|
||||
|
@ -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,9 +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
|
||||
@ -31,9 +37,15 @@ 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
|
||||
import com.twofasapp.feature.home.ui.services.add.AddServiceModal
|
||||
import com.twofasapp.feature.home.ui.services.focus.FocusServiceModal
|
||||
import com.twofasapp.feature.home.ui.services.focus.FocusServiceModalNavArg
|
||||
@ -70,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) {
|
||||
@ -82,7 +100,9 @@ internal fun MainNavHost(
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = startDestination
|
||||
startDestination = startDestination,
|
||||
enterTransition = NavAnimation.Enter,
|
||||
exitTransition = NavAnimation.Exit,
|
||||
) {
|
||||
|
||||
startupNavigation(
|
||||
@ -120,17 +140,21 @@ internal fun MainNavHost(
|
||||
navController.navigate(TrashGraph.route)
|
||||
}
|
||||
|
||||
override fun openNotifications() {
|
||||
navController.navigate(NotificationsGraph.route)
|
||||
}
|
||||
|
||||
override fun openAbout() {
|
||||
navController.navigate(AboutGraph.route)
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -151,18 +175,21 @@ internal fun MainNavHost(
|
||||
)
|
||||
|
||||
appSettingsNavigation()
|
||||
notificationsNavigation()
|
||||
trashNavigation(navController = navController)
|
||||
aboutNavigation(navController = navController)
|
||||
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))
|
||||
@ -170,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!!) {
|
||||
|
@ -37,6 +37,10 @@ internal class MainViewModel(
|
||||
val uiState: MutableStateFlow<MainUiState> = MutableStateFlow(MainUiState())
|
||||
|
||||
init {
|
||||
launchScoped {
|
||||
sessionRepository.markAppInstalled()
|
||||
}
|
||||
|
||||
launchScoped {
|
||||
val destination = when (sessionRepository.isOnboardingDisplayed()) {
|
||||
true -> MainUiState.StartDestination.Home
|
||||
@ -58,7 +62,7 @@ internal class MainViewModel(
|
||||
}
|
||||
|
||||
launchScoped {
|
||||
runSafely { notificationsRepository.fetchNotifications() }
|
||||
runSafely { notificationsRepository.fetchNotifications(sessionRepository.getAppInstallTimestamp()) }
|
||||
}
|
||||
|
||||
launchScoped {
|
||||
|
@ -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")
|
||||
}
|
@ -9,4 +9,6 @@
|
||||
<locale android:name="uk" />
|
||||
<locale android:name="it" />
|
||||
<locale android:name="pl" />
|
||||
<locale android:name="id" />
|
||||
<locale android:name="nl" />
|
||||
</locale-config>
|
@ -7,8 +7,11 @@ import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -16,6 +19,7 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@ -29,6 +33,7 @@ import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.design.theme.ThemeState
|
||||
import com.twofasapp.designsystem.MainAppTheme
|
||||
import com.twofasapp.designsystem.TwTheme
|
||||
import com.twofasapp.designsystem.common.TwSwitch
|
||||
import com.twofasapp.designsystem.common.TwTopAppBar
|
||||
import com.twofasapp.resources.R
|
||||
import com.twofasapp.services.domain.model.Service
|
||||
@ -85,10 +90,46 @@ class BrowserExtensionRequestActivity : BaseComponentActivity() {
|
||||
}
|
||||
|
||||
LazyColumn(modifier = modifier) {
|
||||
item { HeaderItem(browserName = uiState.browserName, payload.domain, modifier = Modifier.animateItemPlacement()) }
|
||||
item {
|
||||
HeaderItem(
|
||||
browserName = uiState.browserName,
|
||||
domain = payload.domain,
|
||||
modifier = Modifier.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
.clickable {
|
||||
viewModel.updateSaveMyChoice(uiState.saveMyChoice.not())
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.browser__save_choice),
|
||||
color = TwTheme.color.onSurfacePrimary,
|
||||
style = TwTheme.typo.body1,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
TwSwitch(
|
||||
checked = uiState.saveMyChoice,
|
||||
onCheckedChange = { viewModel.updateSaveMyChoice(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.suggestedServices.isNotEmpty()) {
|
||||
item { SectionItem(title = stringResource(id = R.string.extension__services_suggested_header), modifier = Modifier.animateItemPlacement()) }
|
||||
item {
|
||||
SectionItem(
|
||||
title = stringResource(id = R.string.extension__services_suggested_header),
|
||||
modifier = Modifier.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
items(uiState.suggestedServices, key = { it.id }) {
|
||||
|
||||
ServiceItem(
|
||||
@ -132,7 +173,7 @@ class BrowserExtensionRequestActivity : BaseComponentActivity() {
|
||||
text = stringResource(id = R.string.browser__request_source_description).format(browserName, domain),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 72.dp, end = 16.dp, top = 24.dp, bottom = 24.dp),
|
||||
.padding(16.dp),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = TwTheme.color.onSurfacePrimary)
|
||||
)
|
||||
}
|
||||
|
@ -6,4 +6,5 @@ data class BrowserExtensionRequestUiState(
|
||||
val browserName: String = "",
|
||||
val suggestedServices: List<Service> = emptyList(),
|
||||
val otherServices: List<Service> = emptyList(),
|
||||
val saveMyChoice: Boolean = false,
|
||||
)
|
@ -10,7 +10,6 @@ import com.twofasapp.services.domain.AssignServiceDomainCase
|
||||
import com.twofasapp.services.domain.GetServicesCase
|
||||
import com.twofasapp.services.domain.model.Service
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@ -23,8 +22,7 @@ internal class BrowserExtensionRequestViewModel(
|
||||
private val browserExtRepository: BrowserExtRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(BrowserExtensionRequestUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val uiState = MutableStateFlow(BrowserExtensionRequestUiState())
|
||||
|
||||
fun init(
|
||||
extensionId: String,
|
||||
@ -38,7 +36,7 @@ internal class BrowserExtensionRequestViewModel(
|
||||
val suggestedServices =
|
||||
matchedServices.plus(DomainMatcher.findServicesSuggestedForDomain(services, domain).minus(matchedServices.toSet()))
|
||||
|
||||
_uiState.update { state ->
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
browserName = browsers.find { it.id == extensionId }?.name.orEmpty(),
|
||||
suggestedServices = suggestedServices,
|
||||
@ -55,9 +53,15 @@ internal class BrowserExtensionRequestViewModel(
|
||||
onFinish: () -> Unit
|
||||
) {
|
||||
viewModelScope.launch(dispatchers.io()) {
|
||||
assignServiceDomainCase(service, domain)
|
||||
if (uiState.value.saveMyChoice) {
|
||||
assignServiceDomainCase(service, domain)
|
||||
}
|
||||
browserExtRepository.deleteTokenRequest(requestId)
|
||||
onFinish.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSaveMyChoice(checked: Boolean) {
|
||||
uiState.update { it.copy(saveMyChoice = checked) }
|
||||
}
|
||||
}
|
@ -2,12 +2,12 @@ package com.twofasapp.buildlogic.version
|
||||
|
||||
object AppConfig {
|
||||
const val minSdk = 23
|
||||
const val targetSdk = 33
|
||||
const val compileSdk = 33
|
||||
const val targetSdk = 34
|
||||
const val compileSdk = 34
|
||||
|
||||
private const val verMajor = 4
|
||||
private const val verMinor = 6
|
||||
private const val verPatch = 3
|
||||
private const val verMinor = 7
|
||||
private const val verPatch = 0
|
||||
private const val verInternal = 0
|
||||
|
||||
const val versionCode = verMajor * 1000000 + verMinor * 10000 + verPatch * 100 + verInternal
|
||||
|
@ -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,18 @@
|
||||
package com.twofasapp.android.navigation
|
||||
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
|
||||
object NavAnimation {
|
||||
|
||||
val Enter: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
|
||||
{ fadeIn(animationSpec = tween(250)) }
|
||||
|
||||
val Exit: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
|
||||
{ fadeOut(animationSpec = tween(250)) }
|
||||
}
|
@ -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}}")
|
||||
}
|
@ -64,4 +64,5 @@ object TwIcons {
|
||||
val Screenshot @Composable get() = painterResource(R.drawable.ic_screenshot)
|
||||
val Keyboard @Composable get() = painterResource(R.drawable.ic_keyboard)
|
||||
val Panorama @Composable get() = painterResource(R.drawable.ic_panorama)
|
||||
val Guide @Composable get() = painterResource(R.drawable.ic_guide)
|
||||
}
|
@ -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
|
||||
@ -31,14 +34,19 @@ abstract class ThemeColors {
|
||||
|
||||
val accentLightBlue: Color = Color(0xFF7F9CFF)
|
||||
val accentIndigo: Color = Color(0xFF5E5CE6)
|
||||
val accentPurple: Color = Color(0xFFD95DDC)
|
||||
val accentPurple: Color = Color(0xFF8C49DE)
|
||||
val accentTurquoise: Color = Color(0xFF2FCFBC)
|
||||
val accentGreen: Color = Color(0xFF03BF38)
|
||||
val accentRed: Color = Color(0xFFED1C24)
|
||||
val accentOrange: Color = Color(0xFFFF7A00)
|
||||
val accentYellow: Color = Color(0xFFFFBA0A)
|
||||
val accentPink: Color = Color(0xFFca49de)
|
||||
val accentBrown: Color = Color(0xFFbd8857)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="248dp"
|
||||
android:height="204dp"
|
||||
android:viewportWidth="248"
|
||||
android:viewportHeight="204">
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
android:pathData="M221.9,51.3c0.1,2.2 0.1,4.3 0.1,6.5c0,66.7 -50.8,143.7 -143.7,143.7v0c-27.4,0 -54.3,-7.8 -77.4,-22.6c4,0.5 8,0.7 12,0.7c22.7,0 44.8,-7.6 62.7,-21.7c-21.6,-0.4 -40.6,-14.5 -47.2,-35.1c7.6,1.5 15.4,1.2 22.8,-0.9c-23.6,-4.8 -40.5,-25.5 -40.5,-49.5c0,-0.2 0,-0.4 0,-0.6c7,3.9 14.9,6.1 22.9,6.3c-22.2,-14.8 -29,-44.3 -15.6,-67.4c25.6,31.5 63.5,50.7 104.1,52.8c-4.1,-17.5 1.5,-35.9 14.6,-48.2c20.3,-19.1 52.3,-18.1 71.4,2.2c11.3,-2.2 22.1,-6.4 32.1,-12.3c-3.8,11.7 -11.7,21.6 -22.2,27.9c10,-1.2 19.8,-3.9 29,-8C240.4,35.3 231.8,44.1 221.9,51.3z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:pathData="M97.3,12H114.94L76.4,56.05L121.74,116H86.24L58.43,79.64L26.61,116H8.96L50.19,68.88L6.69,12H43.09L68.23,45.23L97.3,12ZM91.11,105.44H100.89L37.78,22.01H27.29L91.11,105.44Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
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 |
9
core/designsystem/src/main/res/drawable/ic_guide.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M248,688q53.57,0 104.28,12.5T452,738v-427q-45,-30 -97.62,-46.5Q301.76,248 248,248q-38,0 -74.5,9.5T100,281v434q31,-14 70.5,-20.5T248,688ZM512,738q50,-25 98,-37.5T712,688q38,0 78.5,6t69.5,16v-429q-34,-18 -71.82,-25.5Q750.35,248 712,248q-54,0 -104.5,16.5T512,311v427ZM484.5,842q-7.64,0 -16.57,-1.5Q459,839 452,834q-46,-29 -97.86,-46Q302.27,771 248,771q-34.15,0 -67.07,9.5Q148,790 115,801q-33.1,17 -65.55,-2.16Q17,779.68 17,741v-462q0,-25 11,-46.3 11,-21.3 33,-32.7 44,-19.5 90.39,-27.75 46.39,-8.25 94.55,-8.25Q310,164 370,181.5T482,236q51,-36 110,-54t122.06,-18q47.81,0 93.88,9T898,200q22,11.4 33.5,32.7Q943,254 943,279v473q0,36.94 -33,52.97Q877,821 844,801q-32,-12 -64.9,-21 -32.9,-9 -67.03,-9 -53.35,0 -102.21,17.5Q561,806 516,834q-6,5 -14.93,6.5 -8.93,1.5 -16.57,1.5ZM276,499Z"/>
|
||||
</vector>
|
@ -1,4 +1,9 @@
|
||||
<vector android:height="123.3871dp" android:viewportHeight="204"
|
||||
android:viewportWidth="248" android:width="150dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#1D9BF0" android:pathData="M221.95,51.29c0.15,2.17 0.15,4.34 0.15,6.53c0,66.73 -50.8,143.69 -143.69,143.69v-0.04C50.97,201.51 24.1,193.65 1,178.83c3.99,0.48 8,0.72 12.02,0.73c22.74,0.02 44.83,-7.61 62.72,-21.66c-21.61,-0.41 -40.56,-14.5 -47.18,-35.07c7.57,1.46 15.37,1.16 22.8,-0.87C27.8,117.2 10.85,96.5 10.85,72.46c0,-0.22 0,-0.43 0,-0.64c7.02,3.91 14.88,6.08 22.92,6.32C11.58,63.31 4.74,33.79 18.14,10.71c25.64,31.55 63.47,50.73 104.08,52.76c-4.07,-17.54 1.49,-35.92 14.61,-48.25c20.34,-19.12 52.33,-18.14 71.45,2.19c11.31,-2.23 22.15,-6.38 32.07,-12.26c-3.77,11.69 -11.66,21.62 -22.2,27.93c10.01,-1.18 19.79,-3.86 29,-7.95C240.37,35.29 231.83,44.14 221.95,51.29z"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
android:pathData="M97.3,12H114.94L76.4,56.05L121.74,116H86.24L58.43,79.64L26.61,116H8.96L50.19,68.88L6.69,12H43.09L68.23,45.23L97.3,12ZM91.11,105.44H100.89L37.78,22.01H27.29L91.11,105.44Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
|
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 |