Merge branch 'develop' into feature/material3

This commit is contained in:
Rafał Kobyłko 2023-02-18 17:14:00 +01:00
commit f4d7be59cf
26 changed files with 705 additions and 124 deletions

View File

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />

View File

@ -9,6 +9,7 @@ import com.twofasapp.core.cipher.CipherService
import com.twofasapp.core.cipher.CipherServiceImpl
import com.twofasapp.permissions.CameraPermissionRequest
import com.twofasapp.permissions.CameraPermissionRequestFlow
import com.twofasapp.permissions.NotificationsPermissionRequestFlow
import com.twofasapp.services.analytics.AnalyticsServiceFirebase
import com.twofasapp.services.googledrive.GoogleDriveService
import com.twofasapp.services.googledrive.GoogleDriveServiceImpl
@ -39,6 +40,7 @@ val applicationModule = module {
factory { CameraPermissionRequest(activityContext()) }
factory { CameraPermissionRequestFlow(activityContext()) }
factory { NotificationsPermissionRequestFlow(activityContext()) }
single<Dispatchers> { AppDispatchers() }

View File

@ -109,7 +109,7 @@ class ServiceItem(
}
nextCode.pivotX = 0f
nextCode.text = Spanner("Next token: ")
nextCode.text = Spanner(root.context.getString(R.string.tokens__next_token, ""))
.append(model.formatNextCode(), scaleSize(1.4f))
if (model.shouldShowNextToken.not() || isInEditMode || model.service.authType == ServiceDto.AuthType.HOTP) {

View File

@ -20,7 +20,7 @@
style="@style/Toolbar.Back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="Import from the file" />
app:title="@string/backup__import_backup_file" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -35,6 +35,7 @@ dependencies {
implementation(libs.bundles.fastAdapter)
implementation(libs.bundles.rxJava)
implementation(libs.bundles.appCompat)
implementation(libs.bundles.accompanist)
implementation(libs.lottie)
implementation(libs.timber)
implementation(libs.kotlinCoroutines)

View File

@ -42,6 +42,7 @@ class BrowserExtensionModule : KoinModule {
singleOf(::DeletePairedBrowserCase)
viewModelOf(::BrowserExtensionViewModel)
viewModelOf(::BrowserExtensionPermissionViewModel)
viewModelOf(::PairingScanViewModel)
viewModelOf(::PairingProgressViewModel)
viewModelOf(::BrowserExtensionRequestViewModel)

View File

@ -58,8 +58,9 @@ class BrowserExtensionRequestWorker(
}
),
)
showToast("Code sent successfully")
showToast(context.getString(com.twofasapp.resources.R.string.extension__code_sent_msg))
}
BrowserExtensionRequestPayload.Action.Deny -> {
denyLoginRequestCase.invoke(payload.extensionId, payload.requestId)
}
@ -67,7 +68,7 @@ class BrowserExtensionRequestWorker(
} catch (e: Exception) {
showToast("Error occurred when sending code")
showToast(context.getString(com.twofasapp.resources.R.string.extension__code_sent_error_msg))
}
}

View File

@ -1,19 +1,40 @@
package com.twofasapp.browserextension.ui.main
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
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.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
@ -23,29 +44,71 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.core.content.ContextCompat
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.twofasapp.design.compose.ButtonShape
import com.twofasapp.design.compose.ButtonTextColor
import com.twofasapp.design.compose.HeaderEntry
import com.twofasapp.design.compose.SimpleEntry
import com.twofasapp.design.compose.Toolbar
import com.twofasapp.design.compose.dialogs.InputDialog
import com.twofasapp.design.compose.dialogs.RationaleDialog
import com.twofasapp.designsystem.TwTheme
import com.twofasapp.designsystem.common.TwButton
import com.twofasapp.designsystem.common.TwTopAppBar
import com.twofasapp.designsystem.screen.CommonContent
import com.twofasapp.designsystem.settings.SettingsHeader
import com.twofasapp.designsystem.settings.SettingsLink
import com.twofasapp.locale.TwLocale
import com.twofasapp.extensions.openBrowserApp
import com.twofasapp.navigation.SettingsDirections
import com.twofasapp.navigation.SettingsRouter
import com.twofasapp.resources.R
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
@Composable
fun BrowserExtensionScreen(
openPairingScan: () -> Unit,
openBrowserDetails: (String) -> Unit,
viewModel: BrowserExtensionViewModel = koinViewModel(),
internal fun RequestPermission(
permission: String,
rationaleTitle: String = "",
rationaleText: String = "",
onGranted: () -> Unit = {},
onDismiss: () -> Unit = {},
) {
var showRationale by remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
onGranted()
} else {
showRationale = true
}
}
val permissionCheckResult = ContextCompat.checkSelfPermission(LocalContext.current, permission)
if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
onGranted()
} else {
LaunchedEffect(Unit) {
launcher.launch(permission)
}
}
if (showRationale) {
RationaleDialog(
title = rationaleTitle, text = rationaleText, onDismiss = onDismiss
)
}
}
@Composable
internal fun BrowserExtensionScreen(
viewModel: BrowserExtensionViewModel = get(),
router: SettingsRouter = get()
) {
val uiState = viewModel.uiState.collectAsState().value
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
var askForPermission by remember { mutableStateOf(false) }
viewModel.onPairClick = { openPairingScan() }
Scaffold(
@ -55,24 +118,30 @@ fun BrowserExtensionScreen(
if (uiState.isLoading) return@Scaffold
if (uiState.pairedBrowsers.isEmpty()) {
EmptyScreen(
onPairBrowserClick = openPairingScan,
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
)
EmptyScreen(router = router, padding = padding, onAddClick = { askForPermission = true })
} else {
ContentScreen(
onBrowserClick = openBrowserDetails,
onPairBrowserClick = openPairingScan,
viewModel = viewModel,
uiState = uiState,
padding = padding
router = router,
onAddClick = { askForPermission = true },
padding = padding,
)
}
}
if (askForPermission) {
RequestPermission(
permission = Manifest.permission.CAMERA,
onGranted = { router.navigate(SettingsDirections.PairingScan) },
onDismiss = { askForPermission = false },
rationaleTitle = stringResource(id = R.string.permissions__camera_permission),
rationaleText = stringResource(id = R.string.permissions__camera_permission_description),
)
}
uiState.getMostRecentEvent()?.let {
when (it) {
is BrowserExtensionUiState.Event.ShowSnackbarError -> {
@ -86,28 +155,36 @@ fun BrowserExtensionScreen(
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun ContentScreen(
onBrowserClick: (String) -> Unit,
onPairBrowserClick: () -> Unit,
viewModel: BrowserExtensionViewModel,
uiState: BrowserExtensionUiState,
router: SettingsRouter,
onAddClick: () -> Unit,
padding: PaddingValues,
) {
val activity = LocalContext.current as Activity
val permissionState = rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
LazyColumn(modifier = Modifier.padding(padding)) {
item { SettingsHeader(TwLocale.strings.browserExtPairedDevices) }
items(uiState.pairedBrowsers, key = { it.id }) {
SettingsLink(it.name, onClick = { onBrowserClick(it.id) }, subtitle = TwLocale.formatDate(it.pairedAt))
SimpleEntry(title = it.name,
iconVisibleWhenNotSet = true,
subtitle = it.formatPairedAt(),
click = { router.navigate(SettingsDirections.BrowserDetails(extensionId = it.id)) })
}
item {
TwButton(
text = "+ ${TwLocale.strings.browserExtAddNew}",
onClick = onPairBrowserClick,
height = TwTheme.dimen.buttonHeightSmall,
modifier = Modifier.padding(start = 72.dp, top = 6.dp, bottom = 2.dp)
)
Button(
onClick = { onAddClick() }, shape = ButtonShape(), modifier = Modifier.padding(start = 72.dp, top = 6.dp, bottom = 2.dp)
) {
Text(text = stringResource(id = R.string.browser__add_new).uppercase(), color = ButtonTextColor())
}
}
item { SettingsHeader(TwLocale.strings.browserExtDeviceName) }
@ -121,14 +198,30 @@ private fun ContentScreen(
iconEndClick = { viewModel.onEditDeviceClick() },
)
}
}
if (uiState.showRationaleDialog) {
RationaleDialog(
title = stringResource(id = R.string.permissions__camera_permission),
text = stringResource(id = R.string.permissions__camera_permission_description),
onDismiss = { viewModel.onRationaleDialogDismiss() }
)
if (permissionState.status.isGranted.not()) {
item {
Divider(Modifier.padding(top = 24.dp, bottom = 24.dp))
Text(
text = stringResource(id = R.string.browser__push_notifications_content),
style = MaterialTheme.typography.body2.copy(fontSize = 14.sp, color = MaterialTheme.colors.primary),
modifier = Modifier.padding(start = 72.dp, bottom = 8.dp, end = 16.dp)
)
}
item {
Button(
onClick = {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", activity.packageName, null))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.startActivity(intent)
},
shape = ButtonShape(),
modifier = Modifier.padding(start = 72.dp, top = 6.dp, bottom = 2.dp)
) {
Text(text = "Enable Notifications".uppercase(), color = ButtonTextColor())
}
}
}
}
if (uiState.showEditDeviceDialog) {
@ -144,19 +237,36 @@ private fun ContentScreen(
}
@Composable
private fun EmptyScreen(
onPairBrowserClick: () -> Unit,
modifier: Modifier,
internal fun EmptyScreen(
router: SettingsRouter,
padding: PaddingValues,
onAddClick: () -> Unit,
) {
val uriHandler = LocalUriHandler.current
CommonContent(
image = painterResource(id = R.drawable.browser_extension_start_image),
titleText = TwLocale.strings.browserExtHeader,
descriptionText = "${TwLocale.strings.browserExtBody1}\n${TwLocale.strings.browserExtBody2}",
ctaPrimaryText = TwLocale.strings.browserExtCta,
ctaPrimaryClick = onPairBrowserClick,
description = {
ConstraintLayout(
modifier = Modifier
.fillMaxSize()
.padding(padding)
) {
val (content, pair) = createRefs()
Column(verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
.constrainAs(content) {
top.linkTo(parent.top)
bottom.linkTo(pair.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
.padding(vertical = 16.dp)) {
Image(
painter = painterResource(id = R.drawable.browser_extension_start_image),
contentDescription = null,
modifier = Modifier
.height(130.dp)
.offset(y = (-16).dp)
)
Text(
text = buildAnnotatedString {
append("${TwLocale.strings.browserExtMore1} ")
@ -171,7 +281,38 @@ private fun EmptyScreen(
.padding(top = 16.dp)
.clickable { uriHandler.openUri(TwLocale.links.browserExt) },
)
},
modifier = modifier,
)
Text(
text = stringResource(id = R.string.browser__info_description_first) + "\n\n" + stringResource(id = R.string.browser__info_description_second),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp)
)
Text(
text = buildAnnotatedString {
append(stringResource(id = R.string.browser__more_info) + " ")
withStyle(style = SpanStyle(MaterialTheme.colors.primary)) {
append(stringResource(id = R.string.browser__more_info_link_title))
}
}, style = MaterialTheme.typography.body2, modifier = Modifier
.padding(horizontal = 16.dp)
.align(CenterHorizontally)
.clickable {
activity?.openBrowserApp(url = "https://2fas.com/be")
}, textAlign = TextAlign.Center
)
}
Button(onClick = { onAddClick() }, shape = ButtonShape(), modifier = Modifier
.height(48.dp)
.constrainAs(pair) {
bottom.linkTo(parent.bottom, margin = 16.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
}) {
Text(text = stringResource(id = R.string.browser__pair_with_web_browser).uppercase(), color = ButtonTextColor())
}
}
}

View File

@ -9,8 +9,6 @@ data class BrowserExtensionUiState(
val isLoading: Boolean = true,
val pairedBrowsers: List<PairedBrowser> = emptyList(),
val mobileDevice: MobileDevice? = null,
val isCameraPermissionGranted: Boolean = false,
val showRationaleDialog: Boolean = false,
val showEditDeviceDialog: Boolean = false,
override val events: List<Event> = emptyList(),
) : UiState<BrowserExtensionUiState, BrowserExtensionUiState.Event> {

View File

@ -7,21 +7,15 @@ import com.twofasapp.browserextension.domain.FetchPairedBrowsersCase
import com.twofasapp.browserextension.domain.ObserveMobileDeviceCase
import com.twofasapp.browserextension.domain.ObservePairedBrowsersCase
import com.twofasapp.browserextension.domain.UpdateMobileDeviceCase
import com.twofasapp.permissions.CameraPermissionRequestFlow
import com.twofasapp.permissions.PermissionStatus
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class BrowserExtensionViewModel(
private val dispatchers: Dispatchers,
private val cameraPermissionRequest: CameraPermissionRequestFlow,
private val observeMobileDeviceCase: ObserveMobileDeviceCase,
private val observePairedBrowsersCase: ObservePairedBrowsersCase,
private val updateMobileDeviceCase: UpdateMobileDeviceCase,
@ -57,27 +51,6 @@ class BrowserExtensionViewModel(
}
}
fun onPairBrowserClick() {
cameraPermissionRequest.execute()
.take(1)
.onEach {
when (it) {
PermissionStatus.GRANTED -> {
onPairClick()
}
PermissionStatus.DENIED -> Unit
PermissionStatus.DENIED_NEVER_ASK -> _uiState.update { state ->
state.copy(showRationaleDialog = true)
}
}
}.launchIn(viewModelScope)
}
fun onRationaleDialogDismiss() {
_uiState.update { it.copy(showRationaleDialog = false) }
}
fun onEditDeviceClick() {
_uiState.update { it.copy(showEditDeviceDialog = true) }
}

View File

@ -0,0 +1,121 @@
package com.twofasapp.browserextension.ui.main.permission
import android.Manifest
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import com.twofasapp.browserextension.ui.main.RequestPermission
import com.twofasapp.design.compose.ButtonHeight
import com.twofasapp.design.compose.ButtonShape
import com.twofasapp.design.compose.ButtonTextColor
import com.twofasapp.design.compose.Toolbar
import com.twofasapp.navigation.SettingsDirections
import com.twofasapp.navigation.SettingsRouter
import com.twofasapp.resources.R
import org.koin.androidx.compose.get
@Composable
internal fun BrowserExtensionPermissionScreen(
router: SettingsRouter = get(),
) {
// Launched only on >= TIRAMISU
var askForPermission by remember { mutableStateOf(false) }
Scaffold(
topBar = {
Toolbar(title = stringResource(id = R.string.browser__browser_extension)) {
router.navigate(SettingsDirections.GoBack)
}
}
) { padding ->
ConstraintLayout(
modifier = Modifier
.fillMaxHeight()
.padding(horizontal = 16.dp)
) {
val (content, pair) = createRefs()
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.constrainAs(content) {
top.linkTo(parent.top)
bottom.linkTo(pair.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
.padding(vertical = 16.dp)
) {
Image(
painter = painterResource(id = R.drawable.browser_extension_permission_image),
contentDescription = null,
modifier = Modifier
.height(130.dp)
.offset(y = (-16).dp)
)
Text(
text = stringResource(id = R.string.browser__push_notifications_title),
style = MaterialTheme.typography.h6,
textAlign = TextAlign.Center,
modifier = Modifier
)
Text(
text = stringResource(id = R.string.browser__push_notifications_content),
style = MaterialTheme.typography.body1,
textAlign = TextAlign.Center,
modifier = Modifier
)
}
Button(
onClick = { askForPermission = true },
shape = ButtonShape(),
modifier = Modifier
.height(ButtonHeight())
.constrainAs(pair) {
bottom.linkTo(parent.bottom, margin = 16.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
}) {
Text(text = stringResource(id = R.string.commons__continue).uppercase(), color = ButtonTextColor())
}
if (askForPermission) {
RequestPermission(
permission = Manifest.permission.POST_NOTIFICATIONS,
onGranted = { router.navigateBack() },
onDismiss = {
askForPermission = false
router.navigateBack()
},
rationaleTitle = stringResource(id = R.string.browser__push_notifications_title),
rationaleText = stringResource(id = R.string.browser__push_notifications_content),
)
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.twofasapp.browserextension.ui.main.permission
import androidx.compose.runtime.Composable
class BrowserExtensionPermissionScreenFactory {
@Composable
fun create() {
BrowserExtensionPermissionScreen()
}
}

View File

@ -0,0 +1,28 @@
package com.twofasapp.browserextension.ui.main.permission
import androidx.lifecycle.viewModelScope
import com.twofasapp.base.BaseViewModel
import com.twofasapp.permissions.NotificationsPermissionRequestFlow
import com.twofasapp.permissions.PermissionStatus
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
internal class BrowserExtensionPermissionViewModel(
private val notificationsPermissionRequestFlow: NotificationsPermissionRequestFlow,
) : BaseViewModel() {
val permissionStatus = MutableStateFlow<PermissionStatus?>(null)
fun askForPermission() {
viewModelScope.launch {
notificationsPermissionRequestFlow.execute()
.take(1)
.collect { status ->
println("dupa: $status")
permissionStatus.update { status }
}
}
}
}

View File

@ -1,15 +1,18 @@
package com.twofasapp.browserextension.ui.pairing.progress
import android.app.NotificationManager
import android.os.Build
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -29,16 +32,20 @@ import com.twofasapp.design.compose.AnimatedContent
import com.twofasapp.design.compose.ButtonHeight
import com.twofasapp.design.compose.ButtonShape
import com.twofasapp.design.compose.ButtonTextColor
import com.twofasapp.designsystem.common.TwTopAppBar
import com.twofasapp.design.compose.Toolbar
import com.twofasapp.navigation.SettingsDirections
import com.twofasapp.navigation.SettingsRouter
import com.twofasapp.resources.R
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.compose.get
@Composable
fun PairingProgressScreen(
openMain: () -> Unit,
openPairingScan: () -> Unit,
extensionId: String,
viewModel: PairingProgressViewModel = koinViewModel(),
viewModel: PairingProgressViewModel = get(),
router: SettingsRouter = get(),
notificationManager: NotificationManager = get()
) {
val uiState = viewModel.uiState.collectAsState()
viewModel.pairBrowser(extensionId)
@ -50,16 +57,8 @@ fun PairingProgressScreen(
) { padding ->
AnimatedContent(
condition = uiState.value.isPairing,
contentWhenTrue = { ProgressContent(Modifier.fillMaxSize().padding(padding)) },
contentWhenFalse = {
ResultContent(
onContinueClick = { openMain() },
onScanAgainClick = { openPairingScan() },
uiState.value.isPairingSuccess,
uiState.value.code,
modifier = Modifier.fillMaxSize().padding(padding)
)
},
contentWhenTrue = { ProgressContent() },
contentWhenFalse = { ResultContent(uiState.value.isPairingSuccess, uiState.value.code, router, notificationManager) }
)
}
}
@ -78,7 +77,8 @@ internal fun ResultContent(
onScanAgainClick: () -> Unit = {},
isSuccess: Boolean,
code: Int? = null,
modifier: Modifier
router: SettingsRouter,
notificationManager: NotificationManager,
) {
val image = if (isSuccess) R.drawable.browser_extension_success_image else R.drawable.browser_extension_error_image
@ -96,12 +96,18 @@ internal fun ResultContent(
val cta = if (isSuccess) R.string.commons__continue else R.string.browser__result_error_cta
val ctaAction: () -> Unit = {
if (isSuccess) {
onContinueClick()
val ctaAction: () -> Unit = if (isSuccess) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (notificationManager.areNotificationsEnabled()) {
{ router.navigate(SettingsDirections.GoBack) }
} else {
{ router.navigate(SettingsDirections.Permission) }
}
} else {
onScanAgainClick()
{ router.navigate(SettingsDirections.GoBack) }
}
} else {
{ router.navigate(SettingsDirections.PairingScan) }
}
ConstraintLayout(
modifier = modifier

View File

@ -16,6 +16,7 @@ class PairingProgressViewModel(
private val dispatchers: Dispatchers,
private val registerMobileDeviceCase: RegisterMobileDeviceCase,
private val pairBrowserCase: PairBrowserCase,
private val notificationsPermissionRequestFlow: NotificationsPermissionRequestFlow,
) : BaseViewModel() {
private val _uiState = MutableStateFlow(PairingProgressUiState())

View File

@ -79,7 +79,7 @@ class BrowserExtensionRequestActivity : BaseComponentActivity() {
item { HeaderItem(browserName = uiState.browserName, payload.domain, modifier = Modifier.animateItemPlacement()) }
if (uiState.suggestedServices.isNotEmpty()) {
item { SectionItem(title = "Suggested", modifier = Modifier.animateItemPlacement()) }
item { SectionItem(title = stringResource(id = R.string.extension__services_suggested_header), modifier = Modifier.animateItemPlacement()) }
items(uiState.suggestedServices, key = { it.id }) {
ServiceItem(
@ -94,7 +94,13 @@ class BrowserExtensionRequestActivity : BaseComponentActivity() {
if (uiState.otherServices.isNotEmpty()) {
item {
SectionItem(
title = if (uiState.suggestedServices.isEmpty()) "All services" else "Other services",
title = stringResource(
id = if (uiState.suggestedServices.isEmpty()) {
R.string.extension__services_all_header
} else {
R.string.extension__services_other_header
}
),
modifier = Modifier.animateItemPlacement()
)
}

View File

@ -7,7 +7,7 @@ object AppConfig {
private const val verMajor = 4
private const val verMinor = 2
private const val verPatch = 2
private const val verPatch = 3
private const val verInternal = 0
const val versionCode = verMajor * 1000000 + verMinor * 10000 + verPatch * 100 + verInternal

View File

@ -26,6 +26,7 @@ accompanistPlaceholder = { module = "com.google.accompanist:accompanist-placehol
accompanistSystemUi = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
accompanistPager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
accompanistPagerIndicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" }
accompanistPermissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }
@ -153,6 +154,7 @@ accompanist = [
"accompanistSystemUi",
"accompanistPager",
"accompanistPagerIndicators",
"accompanistPermissions"
]
coil = [
"coil",

View File

@ -0,0 +1,14 @@
package com.twofasapp.permissions
import android.Manifest
import android.app.Activity
import android.os.Build
import androidx.annotation.RequiresApi
import com.twofasapp.permissions.internal.PermissionRequest
import com.twofasapp.permissions.internal.PermissionRequestFlow
class NotificationsPermissionRequestFlow(activity: Activity) : PermissionRequestFlow(activity) {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override val permission: String = Manifest.permission.POST_NOTIFICATIONS
}

View File

@ -0,0 +1,117 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="560dp"
android:height="400dp"
android:viewportWidth="560"
android:viewportHeight="400">
<path
android:pathData="M475.5,22H155.5C143.07,22 133,32.07 133,44.5C133,56.93 143.07,67 155.5,67H475.5C487.93,67 498,56.93 498,44.5C498,32.07 487.93,22 475.5,22Z"
android:fillColor="#A50006"/>
<path
android:pathData="M27,375V87C27,60.49 48.49,39 75,39H415C441.51,39 463,60.49 463,87V132.5"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M463,377.5V312"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M379,83.67H136.67C121.94,83.67 110,71.73 110,57"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M245,67C247.76,67 250,64.76 250,62C250,59.24 247.76,57 245,57C242.24,57 240,59.24 240,62C240,64.76 242.24,67 245,67Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M490.49,235H242.1C232.66,235 225,242.65 225,252.1C225,261.54 232.66,269.19 242.1,269.19H490.49C499.93,269.19 507.59,261.54 507.59,252.1C507.59,242.65 499.93,235 490.49,235Z"
android:fillColor="#FF8095"/>
<path
android:pathData="M210,184.37C210,183.15 210.94,182.17 212.16,182.17C213.38,182.17 214.36,183.14 214.36,184.37V185.31C215.58,183.55 217.35,182 220.3,182C224.58,182 227.07,184.88 227.07,189.27V199.42C227.07,200.64 226.13,201.58 224.91,201.58C223.69,201.58 222.71,200.64 222.71,199.42V190.6C222.71,187.65 221.24,185.96 218.64,185.96C216.04,185.96 214.36,187.72 214.36,190.68V199.43C214.36,200.65 213.39,201.59 212.16,201.59C210.93,201.59 210,200.65 210,199.43V184.38V184.37Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M231,192V191.93C231,186.49 235.32,182 241.15,182C246.98,182 251.27,186.43 251.27,191.86V191.93C251.27,197.33 246.95,201.83 241.08,201.83C235.21,201.83 231,197.4 231,192ZM246.91,192V191.93C246.91,188.58 244.5,185.81 241.08,185.81C237.66,185.81 235.36,188.55 235.36,191.86V191.93C235.36,195.24 237.77,198.01 241.15,198.01C244.53,198.01 246.91,195.27 246.91,192Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M256.59,196.15V186.14H255.87C254.83,186.14 254,185.31 254,184.27C254,183.23 254.83,182.4 255.87,182.4H256.59V179.16C256.59,177.97 257.56,177 258.79,177C260.02,177 260.95,177.97 260.95,179.16V182.4H264.37C265.41,182.4 266.28,183.23 266.28,184.27C266.28,185.31 265.42,186.14 264.37,186.14H260.95V195.46C260.95,197.15 261.81,197.84 263.29,197.84C263.79,197.84 264.23,197.73 264.37,197.73C265.34,197.73 266.2,198.52 266.2,199.53C266.2,200.32 265.66,200.97 265.05,201.22C264.11,201.54 263.21,201.72 262.06,201.72C258.86,201.72 256.59,200.32 256.59,196.14V196.15Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M271,177.12C271,175.82 272.08,175 273.48,175C274.88,175 275.96,175.83 275.96,177.12V177.34C275.96,178.64 274.88,179.5 273.48,179.5C272.08,179.5 271,178.64 271,177.34V177.12ZM271.32,184.21C271.32,182.99 272.26,182.01 273.48,182.01C274.7,182.01 275.68,182.98 275.68,184.21V199.26C275.68,200.48 274.71,201.42 273.48,201.42C272.25,201.42 271.32,200.48 271.32,199.26V184.21Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M281.6,186.23H280.84C279.83,186.23 279,185.44 279,184.43C279,183.42 279.83,182.59 280.84,182.59H281.6V181.19C281.6,179.1 282.14,177.52 283.15,176.51C284.16,175.5 285.56,175 287.43,175C288.3,175 289.01,175.07 289.63,175.18C290.42,175.29 291.14,176.01 291.14,176.98C291.14,177.95 290.31,178.82 289.31,178.78C289.06,178.74 288.7,178.71 288.41,178.71C286.75,178.71 285.89,179.61 285.89,181.59V182.63H289.27C290.31,182.63 291.11,183.42 291.11,184.43C291.11,185.44 290.28,186.23 289.27,186.23H285.96V199.51C285.96,200.7 284.99,201.67 283.76,201.67C282.53,201.67 281.6,200.7 281.6,199.51V186.23ZM294.09,177.38C294.09,176.08 295.17,175.26 296.57,175.26C297.97,175.26 299.05,176.09 299.05,177.38V177.6C299.05,178.9 297.97,179.76 296.57,179.76C295.17,179.76 294.09,178.9 294.09,177.6V177.38ZM294.41,184.47C294.41,183.25 295.35,182.27 296.57,182.27C297.79,182.27 298.77,183.24 298.77,184.47V199.52C298.77,200.74 297.8,201.68 296.57,201.68C295.34,201.68 294.41,200.74 294.41,199.52V184.47Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M304,192V191.93C304,186.53 308.17,182 313.9,182C316.89,182 318.94,182.97 320.56,184.38C320.85,184.63 321.24,185.17 321.24,185.89C321.24,187.01 320.34,187.87 319.22,187.87C318.68,187.87 318.21,187.65 317.93,187.44C316.81,186.5 315.59,185.82 313.86,185.82C310.69,185.82 308.35,188.56 308.35,191.87V191.94C308.35,195.32 310.69,198.02 314.04,198.02C315.77,198.02 317.1,197.34 318.29,196.33C318.54,196.11 318.97,195.86 319.48,195.86C320.52,195.86 321.35,196.72 321.35,197.77C321.35,198.35 321.13,198.81 320.74,199.14C319.05,200.76 317,201.84 313.83,201.84C308.18,201.84 304,197.41 304,192.01V192Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M324,195.79V195.72C324,191.62 327.2,189.6 331.85,189.6C333.97,189.6 335.49,189.92 336.96,190.39V189.92C336.96,187.22 335.3,185.78 332.25,185.78C330.59,185.78 329.23,186.07 328.04,186.54C327.79,186.61 327.57,186.65 327.36,186.65C326.35,186.65 325.53,185.86 325.53,184.85C325.53,184.06 326.07,183.37 326.72,183.12C328.52,182.44 330.35,182 332.84,182C335.68,182 337.81,182.76 339.14,184.12C340.54,185.49 341.19,187.5 341.19,189.99V199.35C341.19,200.54 340.25,201.44 339.07,201.44C337.81,201.44 336.95,200.58 336.95,199.6V198.88C335.65,200.43 333.67,201.65 330.76,201.65C327.2,201.65 324.03,199.6 324.03,195.78L324,195.79ZM337.03,194.42V193.12C335.91,192.69 334.44,192.36 332.71,192.36C329.9,192.36 328.25,193.55 328.25,195.53V195.6C328.25,197.44 329.87,198.48 331.96,198.48C334.84,198.48 337.04,196.82 337.04,194.41L337.03,194.42Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M347.59,196.15V186.14H346.87C345.83,186.14 345,185.31 345,184.27C345,183.23 345.83,182.4 346.87,182.4H347.59V179.16C347.59,177.97 348.56,177 349.79,177C351.02,177 351.95,177.97 351.95,179.16V182.4H355.37C356.41,182.4 357.28,183.23 357.28,184.27C357.28,185.31 356.42,186.14 355.37,186.14H351.95V195.46C351.95,197.15 352.81,197.84 354.29,197.84C354.79,197.84 355.23,197.73 355.37,197.73C356.34,197.73 357.21,198.52 357.21,199.53C357.21,200.32 356.67,200.97 356.06,201.22C355.12,201.54 354.23,201.72 353.07,201.72C349.87,201.72 347.6,200.32 347.6,196.14L347.59,196.15Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M361,177.12C361,175.82 362.08,175 363.48,175C364.88,175 365.96,175.83 365.96,177.12V177.34C365.96,178.64 364.88,179.5 363.48,179.5C362.08,179.5 361,178.64 361,177.34V177.12ZM361.32,184.21C361.32,182.99 362.26,182.01 363.48,182.01C364.7,182.01 365.68,182.98 365.68,184.21V199.26C365.68,200.48 364.71,201.42 363.48,201.42C362.25,201.42 361.32,200.48 361.32,199.26V184.21Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M370.01,192V191.93C370.01,186.49 374.33,182 380.16,182C385.99,182 390.27,186.43 390.27,191.86V191.93C390.27,197.33 385.95,201.83 380.08,201.83C374.21,201.83 370,197.4 370,192H370.01ZM385.92,192V191.93C385.92,188.58 383.51,185.81 380.09,185.81C376.67,185.81 374.37,188.55 374.37,191.86V191.93C374.37,195.24 376.78,198.01 380.16,198.01C383.54,198.01 385.92,195.27 385.92,192Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M395,184.37C395,183.15 395.94,182.17 397.16,182.17C398.38,182.17 399.36,183.14 399.36,184.37V185.31C400.58,183.55 402.35,182 405.3,182C409.58,182 412.07,184.88 412.07,189.27V199.42C412.07,200.64 411.13,201.58 409.91,201.58C408.69,201.58 407.71,200.64 407.71,199.42V190.6C407.71,187.65 406.24,185.96 403.64,185.96C401.04,185.96 399.36,187.72 399.36,190.68V199.43C399.36,200.65 398.39,201.59 397.16,201.59C395.93,201.59 395,200.65 395,199.43V184.38V184.37Z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M102.64,150H501C518.67,150 533,164.33 533,182V257C533,274.67 518.67,289 501,289H83C65.33,289 51,274.67 51,257V182C51,178 51.73,174.18 53.07,170.65"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M172.04,283.55L148.02,301.25C143.63,304.49 137.63,304.49 133.24,301.25L109.22,283.55C100.28,276.96 95,266.52 95,255.41V202.1C95,198.61 97.26,195.52 100.6,194.47L121.02,188.02C133.79,183.99 147.48,183.99 160.25,188.02L180.67,194.47C184,195.52 186.27,198.61 186.27,202.1V255.41C186.27,266.52 180.99,276.96 172.05,283.55H172.04Z"
android:fillColor="#ED1B24"/>
<path
android:pathData="M154.01,229C154.01,221.84 148.22,216.03 141.07,216C133.87,215.97 128,221.81 128,229C128,233.78 130.58,237.96 134.43,240.22C135.05,240.58 135.37,241.3 135.2,241.99L132.02,255.38C131.78,256.39 132.55,257.37 133.59,257.37H148.42C149.46,257.37 150.23,256.4 149.99,255.38L146.81,241.99C146.64,241.29 146.96,240.58 147.58,240.22C151.42,237.96 154,233.79 154,229.01L154.01,229Z"
android:fillColor="#ED1B24"/>
<path
android:pathData="M145.89,229.45C145.89,221.5 139.46,215.04 131.52,215C123.52,214.96 117,221.45 117,229.45C117,234.76 119.87,239.4 124.14,241.91C124.83,242.31 125.18,243.11 125,243.88L121.47,258.76C121.2,259.89 122.06,260.97 123.21,260.97H139.69C140.85,260.97 141.7,259.89 141.43,258.76L137.9,243.88C137.72,243.11 138.07,242.31 138.76,241.91C143.03,239.4 145.9,234.76 145.9,229.45H145.89Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M160.04,277.55L136.02,295.25C131.63,298.49 125.63,298.49 121.24,295.25L97.22,277.55C88.28,270.96 83,260.52 83,249.41V196.1C83,192.61 85.26,189.52 88.6,188.47L109.02,182.02C121.79,177.99 135.48,177.99 148.25,182.02L168.67,188.47C172,189.52 174.27,192.61 174.27,196.1V249.41C174.27,260.52 168.99,270.96 160.05,277.55H160.04Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M142.01,223C142.01,215.84 136.22,210.03 129.07,210C121.92,209.97 116,215.81 116,223C116,227.78 118.58,231.96 122.43,234.22C123.05,234.58 123.37,235.3 123.2,235.99L120.02,249.38C119.78,250.39 120.55,251.37 121.59,251.37H136.42C137.46,251.37 138.23,250.4 137.99,249.38L134.81,235.99C134.64,235.29 134.96,234.58 135.58,234.22C139.42,231.96 142,227.79 142,223.01L142.01,223Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M505.59,219H226.1C221.38,219 217.11,220.91 214.01,224.01C210.92,227.1 209,231.38 209,236.1C209,242.28 212.28,247.7 217.2,250.7C219.79,252.29 222.84,253.2 226.09,253.2H329.75H328.58"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M486,311.67H76.67C61.94,311.67 50,299.73 50,285"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,117 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="560dp"
android:height="400dp"
android:viewportWidth="560"
android:viewportHeight="400">
<path
android:pathData="M475.5,22H155.5C143.07,22 133,32.07 133,44.5C133,56.93 143.07,67 155.5,67H475.5C487.93,67 498,56.93 498,44.5C498,32.07 487.93,22 475.5,22Z"
android:fillColor="#A50006"/>
<path
android:pathData="M27,375V87C27,60.49 48.49,39 75,39H415C441.51,39 463,60.49 463,87V132.5"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M463,377.5V312"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M379,83.67H136.67C121.94,83.67 110,71.73 110,57"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M245,67C247.76,67 250,64.76 250,62C250,59.24 247.76,57 245,57C242.24,57 240,59.24 240,62C240,64.76 242.24,67 245,67Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M490.49,235H242.1C232.66,235 225,242.65 225,252.1C225,261.54 232.66,269.19 242.1,269.19H490.49C499.93,269.19 507.59,261.54 507.59,252.1C507.59,242.65 499.93,235 490.49,235Z"
android:fillColor="#FF8095"/>
<path
android:pathData="M210,184.37C210,183.15 210.94,182.17 212.16,182.17C213.38,182.17 214.36,183.14 214.36,184.37V185.31C215.58,183.55 217.35,182 220.3,182C224.58,182 227.07,184.88 227.07,189.27V199.42C227.07,200.64 226.13,201.58 224.91,201.58C223.69,201.58 222.71,200.64 222.71,199.42V190.6C222.71,187.65 221.24,185.96 218.64,185.96C216.04,185.96 214.36,187.72 214.36,190.68V199.43C214.36,200.65 213.39,201.59 212.16,201.59C210.93,201.59 210,200.65 210,199.43V184.38V184.37Z"
android:fillColor="#000000"/>
<path
android:pathData="M231,192V191.93C231,186.49 235.32,182 241.15,182C246.98,182 251.27,186.43 251.27,191.86V191.93C251.27,197.33 246.95,201.83 241.08,201.83C235.21,201.83 231,197.4 231,192ZM246.91,192V191.93C246.91,188.58 244.5,185.81 241.08,185.81C237.66,185.81 235.36,188.55 235.36,191.86V191.93C235.36,195.24 237.77,198.01 241.15,198.01C244.53,198.01 246.91,195.27 246.91,192Z"
android:fillColor="#000000"/>
<path
android:pathData="M256.59,196.15V186.14H255.87C254.83,186.14 254,185.31 254,184.27C254,183.23 254.83,182.4 255.87,182.4H256.59V179.16C256.59,177.97 257.56,177 258.79,177C260.02,177 260.95,177.97 260.95,179.16V182.4H264.37C265.41,182.4 266.28,183.23 266.28,184.27C266.28,185.31 265.42,186.14 264.37,186.14H260.95V195.46C260.95,197.15 261.81,197.84 263.29,197.84C263.79,197.84 264.23,197.73 264.37,197.73C265.34,197.73 266.2,198.52 266.2,199.53C266.2,200.32 265.66,200.97 265.05,201.22C264.11,201.54 263.21,201.72 262.06,201.72C258.86,201.72 256.59,200.32 256.59,196.14V196.15Z"
android:fillColor="#000000"/>
<path
android:pathData="M271,177.12C271,175.82 272.08,175 273.48,175C274.88,175 275.96,175.83 275.96,177.12V177.34C275.96,178.64 274.88,179.5 273.48,179.5C272.08,179.5 271,178.64 271,177.34V177.12ZM271.32,184.21C271.32,182.99 272.26,182.01 273.48,182.01C274.7,182.01 275.68,182.98 275.68,184.21V199.26C275.68,200.48 274.71,201.42 273.48,201.42C272.25,201.42 271.32,200.48 271.32,199.26V184.21Z"
android:fillColor="#000000"/>
<path
android:pathData="M281.6,186.23H280.84C279.83,186.23 279,185.44 279,184.43C279,183.42 279.83,182.59 280.84,182.59H281.6V181.19C281.6,179.1 282.14,177.52 283.15,176.51C284.16,175.5 285.56,175 287.43,175C288.3,175 289.01,175.07 289.63,175.18C290.42,175.29 291.14,176.01 291.14,176.98C291.14,177.95 290.31,178.82 289.31,178.78C289.06,178.74 288.7,178.71 288.41,178.71C286.75,178.71 285.89,179.61 285.89,181.59V182.63H289.27C290.31,182.63 291.11,183.42 291.11,184.43C291.11,185.44 290.28,186.23 289.27,186.23H285.96V199.51C285.96,200.7 284.99,201.67 283.76,201.67C282.53,201.67 281.6,200.7 281.6,199.51V186.23ZM294.09,177.38C294.09,176.08 295.17,175.26 296.57,175.26C297.97,175.26 299.05,176.09 299.05,177.38V177.6C299.05,178.9 297.97,179.76 296.57,179.76C295.17,179.76 294.09,178.9 294.09,177.6V177.38ZM294.41,184.47C294.41,183.25 295.35,182.27 296.57,182.27C297.79,182.27 298.77,183.24 298.77,184.47V199.52C298.77,200.74 297.8,201.68 296.57,201.68C295.34,201.68 294.41,200.74 294.41,199.52V184.47Z"
android:fillColor="#000000"/>
<path
android:pathData="M304,192V191.93C304,186.53 308.17,182 313.9,182C316.89,182 318.94,182.97 320.56,184.38C320.85,184.63 321.24,185.17 321.24,185.89C321.24,187.01 320.34,187.87 319.22,187.87C318.68,187.87 318.21,187.65 317.93,187.44C316.81,186.5 315.59,185.82 313.86,185.82C310.69,185.82 308.35,188.56 308.35,191.87V191.94C308.35,195.32 310.69,198.02 314.04,198.02C315.77,198.02 317.1,197.34 318.29,196.33C318.54,196.11 318.97,195.86 319.48,195.86C320.52,195.86 321.35,196.72 321.35,197.77C321.35,198.35 321.13,198.81 320.74,199.14C319.05,200.76 317,201.84 313.83,201.84C308.18,201.84 304,197.41 304,192.01V192Z"
android:fillColor="#000000"/>
<path
android:pathData="M324,195.79V195.72C324,191.62 327.2,189.6 331.85,189.6C333.97,189.6 335.49,189.92 336.96,190.39V189.92C336.96,187.22 335.3,185.78 332.25,185.78C330.59,185.78 329.23,186.07 328.04,186.54C327.79,186.61 327.57,186.65 327.36,186.65C326.35,186.65 325.53,185.86 325.53,184.85C325.53,184.06 326.07,183.37 326.72,183.12C328.52,182.44 330.35,182 332.84,182C335.68,182 337.81,182.76 339.14,184.12C340.54,185.49 341.19,187.5 341.19,189.99V199.35C341.19,200.54 340.25,201.44 339.07,201.44C337.81,201.44 336.95,200.58 336.95,199.6V198.88C335.65,200.43 333.67,201.65 330.76,201.65C327.2,201.65 324.03,199.6 324.03,195.78L324,195.79ZM337.03,194.42V193.12C335.91,192.69 334.44,192.36 332.71,192.36C329.9,192.36 328.25,193.55 328.25,195.53V195.6C328.25,197.44 329.87,198.48 331.96,198.48C334.84,198.48 337.04,196.82 337.04,194.41L337.03,194.42Z"
android:fillColor="#000000"/>
<path
android:pathData="M347.59,196.15V186.14H346.87C345.83,186.14 345,185.31 345,184.27C345,183.23 345.83,182.4 346.87,182.4H347.59V179.16C347.59,177.97 348.56,177 349.79,177C351.02,177 351.95,177.97 351.95,179.16V182.4H355.37C356.41,182.4 357.28,183.23 357.28,184.27C357.28,185.31 356.42,186.14 355.37,186.14H351.95V195.46C351.95,197.15 352.81,197.84 354.29,197.84C354.79,197.84 355.23,197.73 355.37,197.73C356.34,197.73 357.21,198.52 357.21,199.53C357.21,200.32 356.67,200.97 356.06,201.22C355.12,201.54 354.23,201.72 353.07,201.72C349.87,201.72 347.6,200.32 347.6,196.14L347.59,196.15Z"
android:fillColor="#000000"/>
<path
android:pathData="M361,177.12C361,175.82 362.08,175 363.48,175C364.88,175 365.96,175.83 365.96,177.12V177.34C365.96,178.64 364.88,179.5 363.48,179.5C362.08,179.5 361,178.64 361,177.34V177.12ZM361.32,184.21C361.32,182.99 362.26,182.01 363.48,182.01C364.7,182.01 365.68,182.98 365.68,184.21V199.26C365.68,200.48 364.71,201.42 363.48,201.42C362.25,201.42 361.32,200.48 361.32,199.26V184.21Z"
android:fillColor="#000000"/>
<path
android:pathData="M370.01,192V191.93C370.01,186.49 374.33,182 380.16,182C385.99,182 390.27,186.43 390.27,191.86V191.93C390.27,197.33 385.95,201.83 380.08,201.83C374.21,201.83 370,197.4 370,192H370.01ZM385.92,192V191.93C385.92,188.58 383.51,185.81 380.09,185.81C376.67,185.81 374.37,188.55 374.37,191.86V191.93C374.37,195.24 376.78,198.01 380.16,198.01C383.54,198.01 385.92,195.27 385.92,192Z"
android:fillColor="#000000"/>
<path
android:pathData="M395,184.37C395,183.15 395.94,182.17 397.16,182.17C398.38,182.17 399.36,183.14 399.36,184.37V185.31C400.58,183.55 402.35,182 405.3,182C409.58,182 412.07,184.88 412.07,189.27V199.42C412.07,200.64 411.13,201.58 409.91,201.58C408.69,201.58 407.71,200.64 407.71,199.42V190.6C407.71,187.65 406.24,185.96 403.64,185.96C401.04,185.96 399.36,187.72 399.36,190.68V199.43C399.36,200.65 398.39,201.59 397.16,201.59C395.93,201.59 395,200.65 395,199.43V184.38V184.37Z"
android:fillColor="#000000"/>
<path
android:pathData="M102.64,150H501C518.67,150 533,164.33 533,182V257C533,274.67 518.67,289 501,289H83C65.33,289 51,274.67 51,257V182C51,178 51.73,174.18 53.07,170.65"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M172.04,283.55L148.02,301.25C143.63,304.49 137.63,304.49 133.24,301.25L109.22,283.55C100.28,276.96 95,266.52 95,255.41V202.1C95,198.61 97.26,195.52 100.6,194.47L121.02,188.02C133.79,183.99 147.48,183.99 160.25,188.02L180.67,194.47C184,195.52 186.27,198.61 186.27,202.1V255.41C186.27,266.52 180.99,276.96 172.05,283.55H172.04Z"
android:fillColor="#ED1B24"/>
<path
android:pathData="M154.01,229C154.01,221.84 148.22,216.03 141.07,216C133.87,215.97 128,221.81 128,229C128,233.78 130.58,237.96 134.43,240.22C135.05,240.58 135.37,241.3 135.2,241.99L132.02,255.38C131.78,256.39 132.55,257.37 133.59,257.37H148.42C149.46,257.37 150.23,256.4 149.99,255.38L146.81,241.99C146.64,241.29 146.96,240.58 147.58,240.22C151.42,237.96 154,233.79 154,229.01L154.01,229Z"
android:fillColor="#ED1B24"/>
<path
android:pathData="M145.89,229.45C145.89,221.5 139.46,215.04 131.52,215C123.52,214.96 117,221.45 117,229.45C117,234.76 119.87,239.4 124.14,241.91C124.83,242.31 125.18,243.11 125,243.88L121.47,258.76C121.2,259.89 122.06,260.97 123.21,260.97H139.69C140.85,260.97 141.7,259.89 141.43,258.76L137.9,243.88C137.72,243.11 138.07,242.31 138.76,241.91C143.03,239.4 145.9,234.76 145.9,229.45H145.89Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M160.04,277.55L136.02,295.25C131.63,298.49 125.63,298.49 121.24,295.25L97.22,277.55C88.28,270.96 83,260.52 83,249.41V196.1C83,192.61 85.26,189.52 88.6,188.47L109.02,182.02C121.79,177.99 135.48,177.99 148.25,182.02L168.67,188.47C172,189.52 174.27,192.61 174.27,196.1V249.41C174.27,260.52 168.99,270.96 160.05,277.55H160.04Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M142.01,223C142.01,215.84 136.22,210.03 129.07,210C121.92,209.97 116,215.81 116,223C116,227.78 118.58,231.96 122.43,234.22C123.05,234.58 123.37,235.3 123.2,235.99L120.02,249.38C119.78,250.39 120.55,251.37 121.59,251.37H136.42C137.46,251.37 138.23,250.4 137.99,249.38L134.81,235.99C134.64,235.29 134.96,234.58 135.58,234.22C139.42,231.96 142,227.79 142,223.01L142.01,223Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M505.59,219H226.1C221.38,219 217.11,220.91 214.01,224.01C210.92,227.1 209,231.38 209,236.1C209,242.28 212.28,247.7 217.2,250.7C219.79,252.29 222.84,253.2 226.09,253.2H329.75H328.58"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M486,311.67H76.67C61.94,311.67 50,299.73 50,285"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View File

@ -3,7 +3,14 @@ package com.twofasapp.services.ui.badge
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
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.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
@ -14,6 +21,7 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.twofasapp.design.compose.dialogs.internal.BaseDialog
import com.twofasapp.design.theme.textPrimary
@ -59,7 +67,19 @@ internal fun ColorBadgeDialog(
Spacer(modifier = Modifier.width(24.dp))
Text(
text = it.displayName,
stringResource(
id = when (it) {
Tint.Default -> com.twofasapp.resources.R.string.color__neutral
Tint.LightBlue -> com.twofasapp.resources.R.string.color__light_blue
Tint.Indigo -> com.twofasapp.resources.R.string.color__indigo
Tint.Purple -> com.twofasapp.resources.R.string.color__purple
Tint.Turquoise -> com.twofasapp.resources.R.string.color__turquoise
Tint.Green -> com.twofasapp.resources.R.string.color__green
Tint.Red -> com.twofasapp.resources.R.string.color__red
Tint.Orange -> com.twofasapp.resources.R.string.color__orange
Tint.Yellow -> com.twofasapp.resources.R.string.color__yellow
}
),
style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.textPrimary),
modifier = Modifier
.align(CenterVertically)

View File

@ -75,7 +75,7 @@ internal fun ChangeBrandScreen(
topBar = {
ToolbarWithSearch(
title = stringResource(id = R.string.customization_change_brand),
searchHint = "Search",
searchHint = stringResource(id = R.string.commons__search),
onSearchValueChanged = { brandViewModel.applySearchFilter(it) }
) { router.navigateBack() }
}

View File

@ -57,7 +57,7 @@ internal fun ChangeLabelScreen(
Toolbar(title = stringResource(id = R.string.customization_edit_label), actions = {
TextButton(
onClick = {
viewModel.updateLabel(labelText.value.uppercase(), labelTint.value?: Tint.LightBlue)
viewModel.updateLabel(labelText.value.uppercase(), labelTint.value ?: Tint.LightBlue)
router.navigateBack()
},
enabled = labelText.value.isNotBlank() && labelText.value.isNotEmpty()
@ -85,7 +85,7 @@ internal fun ChangeLabelScreen(
Box(modifier = Modifier.padding(24.dp)) {
TextFieldOutlined(
value = labelText.value,
label = { Text(text = "Label (1 or 2 characters)") },
label = { Text(text = stringResource(id = R.string.tokens__label_characters_title)) },
maxChars = 2,
onValueChange = { labelText.value = it.uppercase() },
keyboardOptions = KeyboardOptions.Default.copy(capitalization = KeyboardCapitalization.Characters),
@ -112,7 +112,19 @@ internal fun ChangeLabelScreen(
Spacer(modifier = Modifier.height(8.dp))
Text(
text = it.displayName,
stringResource(
id = when (it) {
Tint.Default -> com.twofasapp.resources.R.string.color__neutral
Tint.LightBlue -> com.twofasapp.resources.R.string.color__light_blue
Tint.Indigo -> com.twofasapp.resources.R.string.color__indigo
Tint.Purple -> com.twofasapp.resources.R.string.color__purple
Tint.Turquoise -> com.twofasapp.resources.R.string.color__turquoise
Tint.Green -> com.twofasapp.resources.R.string.color__green
Tint.Red -> com.twofasapp.resources.R.string.color__red
Tint.Orange -> com.twofasapp.resources.R.string.color__orange
Tint.Yellow -> com.twofasapp.resources.R.string.color__yellow
}
),
style = MaterialTheme.typography.caption.copy(color = MaterialTheme.colors.textPrimary),
modifier = Modifier
.align(Alignment.CenterHorizontally)

View File

@ -23,7 +23,6 @@ import com.twofasapp.design.compose.Toolbar
import com.twofasapp.design.compose.dialogs.ConfirmDialog
import com.twofasapp.design.theme.divider
import com.twofasapp.design.theme.textSecondary
import com.twofasapp.navigation.ServiceDirections
import com.twofasapp.navigation.ServiceRouter
import com.twofasapp.resources.R
import com.twofasapp.services.ui.ServiceViewModel
@ -43,12 +42,12 @@ internal fun DomainAssignmentScreen(
Scaffold(
topBar = { Toolbar(title = stringResource(id = R.string.browser__browser_extension)) { router.navigateBack() } }
) {
) { _ ->
if (showConfirmDialog.value) {
ConfirmDialog(
title = "Remove domain?",
text = "The next time you use the browser extension to login to ${clickedDomainName.value}, youll be asked to pair this domain again.",
title = stringResource(id = R.string.browser__deleting_extension_pairing_title),
text = stringResource(id = R.string.browser__deleting_extension_pairing_content, clickedDomainName.value),
onPositive = {
showConfirmDialog.value = false
viewModel.deleteDomainAssignment(clickedDomainName.value)
@ -65,7 +64,7 @@ internal fun DomainAssignmentScreen(
LazyColumn {
item {
Text(
text = "List of paired domains.",
text = stringResource(id = R.string.browser__paired_domains_list_title),
modifier = Modifier
.fillMaxWidth()
.padding(start = 72.dp, end = 16.dp, top = 24.dp, bottom = 24.dp),

View File

@ -135,11 +135,19 @@ internal fun ServiceScreen(
}
Scaffold(topBar = {
Toolbar(title = if (service.id == 0L) "Add service" else "Customize service", actions = {
TextButton(
onClick = {
if (service.id == 0L) {
viewModel.tryInsertService(replaceIfExists = false)
Toolbar(
title = if (service.id == 0L) activity!!.getString(R.string.tokens__add_service_title) else activity!!.getString(R.string.tokens__customize_service_title),
actions = {
TextButton(
onClick = {
if (service.id == 0L) {
viewModel.tryInsertService(replaceIfExists = false)
} else {
viewModel.saveService()
}
},
enabled = if (service.id == 0L) {
uiState.isInputNameValid && uiState.isInputSecretValid && uiState.isInputInfoValid
} else {
viewModel.saveService()
}
@ -153,7 +161,7 @@ internal fun ServiceScreen(
Text(text = stringResource(id = R.string.commons__save))
}
}) {
}) {
activity?.onBackPressed()
}
}) { padding ->