Add notifications permission

This commit is contained in:
Rafał Kobyłko 2023-02-17 19:08:25 +01:00
parent 7927edf99b
commit 43ad8d9114
18 changed files with 633 additions and 92 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

@ -11,6 +11,7 @@ import com.twofasapp.environment.AppConfig
import com.twofasapp.externalimport.domain.GoogleAuthenticatorImporter
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
@ -43,6 +44,7 @@ val applicationModule = module {
factory { CameraPermissionRequest(activityContext()) }
factory { CameraPermissionRequestFlow(activityContext()) }
factory { NotificationsPermissionRequestFlow(activityContext()) }
single<Dispatchers> { AppDispatchers() }

View File

@ -6,6 +6,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import com.twofasapp.browserextension.ui.browser.BrowserDetailsScreenFactory
import com.twofasapp.browserextension.ui.main.BrowserExtensionScreenFactory
import com.twofasapp.browserextension.ui.main.permission.BrowserExtensionPermissionScreenFactory
import com.twofasapp.browserextension.ui.pairing.progress.PairingProgressScreenFactory
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
import com.twofasapp.settings.ui.main.SettingsMainScreenFactory
@ -19,6 +20,7 @@ class SettingsRouterImpl(
private val pairingProgressScreenFactory: PairingProgressScreenFactory,
private val pairingScanScreenFactory: PairingScanScreenFactory,
private val browserDetailsScreenFactory: BrowserDetailsScreenFactory,
private val browserExtensionPermissionScreenFactory: BrowserExtensionPermissionScreenFactory,
) : SettingsRouter() {
companion object {
@ -29,6 +31,7 @@ class SettingsRouterImpl(
private const val BROWSER_EXTENSION = "browser_extension"
private const val BROWSER_DETAILS = "browser_details/{$ARG_EXTENSION_ID}"
private const val PAIRING_SCAN = "pairing_scan"
private const val PERMISSION = "permission"
private const val PAIRING_PROGRESS = "pairing_progress/{$ARG_EXTENSION_ID}"
}
@ -40,6 +43,7 @@ class SettingsRouterImpl(
browserDetailsScreenFactory.create(it.arguments?.getString(ARG_EXTENSION_ID).orEmpty())
})
builder.composable(route = PAIRING_SCAN, content = { pairingScanScreenFactory.create() })
builder.composable(route = PERMISSION, content = { browserExtensionPermissionScreenFactory.create() })
builder.composable(route = PAIRING_PROGRESS, content = {
pairingProgressScreenFactory.create(it.arguments?.getString(ARG_EXTENSION_ID).orEmpty())
})
@ -59,6 +63,7 @@ class SettingsRouterImpl(
SettingsDirections.Theme -> navController.navigate(THEME)
SettingsDirections.BrowserExtension -> navController.navigate(BROWSER_EXTENSION)
SettingsDirections.PairingScan -> navController.navigate(PAIRING_SCAN) { popUpTo(BROWSER_EXTENSION) }
SettingsDirections.Permission -> navController.navigate(PERMISSION) { popUpTo(BROWSER_EXTENSION) }
is SettingsDirections.PairingProgress -> navController.navigate(
PAIRING_PROGRESS.replace("{$ARG_EXTENSION_ID}", direction.extensionId)

View File

@ -34,6 +34,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

@ -4,7 +4,20 @@ import com.twofasapp.browserextension.data.BrowserExtensionLocalData
import com.twofasapp.browserextension.data.BrowserExtensionLocalDataImpl
import com.twofasapp.browserextension.data.BrowserExtensionRemoteData
import com.twofasapp.browserextension.data.BrowserExtensionRemoteDataImpl
import com.twofasapp.browserextension.domain.*
import com.twofasapp.browserextension.domain.ApproveLoginRequestCase
import com.twofasapp.browserextension.domain.DeletePairedBrowserCase
import com.twofasapp.browserextension.domain.DenyLoginRequestCase
import com.twofasapp.browserextension.domain.EncryptCodeCase
import com.twofasapp.browserextension.domain.FetchPairedBrowsersCase
import com.twofasapp.browserextension.domain.FetchTokenRequestsCase
import com.twofasapp.browserextension.domain.FetchTokenRequestsCaseImpl
import com.twofasapp.browserextension.domain.ObserveMobileDeviceCase
import com.twofasapp.browserextension.domain.ObserveMobileDeviceCaseImpl
import com.twofasapp.browserextension.domain.ObservePairedBrowsersCase
import com.twofasapp.browserextension.domain.ObservePairedBrowsersCaseImpl
import com.twofasapp.browserextension.domain.PairBrowserCase
import com.twofasapp.browserextension.domain.RegisterMobileDeviceCase
import com.twofasapp.browserextension.domain.UpdateMobileDeviceCase
import com.twofasapp.browserextension.domain.repository.BrowserExtensionRepository
import com.twofasapp.browserextension.domain.repository.BrowserExtensionRepositoryImpl
import com.twofasapp.browserextension.notification.ShowBrowserExtensionRequestNotificationCaseImpl
@ -12,6 +25,8 @@ import com.twofasapp.browserextension.ui.browser.BrowserDetailsScreenFactory
import com.twofasapp.browserextension.ui.browser.BrowserDetailsViewModel
import com.twofasapp.browserextension.ui.main.BrowserExtensionScreenFactory
import com.twofasapp.browserextension.ui.main.BrowserExtensionViewModel
import com.twofasapp.browserextension.ui.main.permission.BrowserExtensionPermissionScreenFactory
import com.twofasapp.browserextension.ui.main.permission.BrowserExtensionPermissionViewModel
import com.twofasapp.browserextension.ui.pairing.progress.PairingProgressScreenFactory
import com.twofasapp.browserextension.ui.pairing.progress.PairingProgressViewModel
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
@ -46,6 +61,7 @@ class BrowserExtensionModule : KoinModule {
singleOf(::DeletePairedBrowserCase)
viewModelOf(::BrowserExtensionViewModel)
viewModelOf(::BrowserExtensionPermissionViewModel)
viewModelOf(::PairingScanViewModel)
viewModelOf(::PairingProgressViewModel)
viewModelOf(::BrowserExtensionRequestViewModel)
@ -55,5 +71,6 @@ class BrowserExtensionModule : KoinModule {
singleOf(::PairingProgressScreenFactory)
singleOf(::PairingScanScreenFactory)
singleOf(::BrowserDetailsScreenFactory)
singleOf(::BrowserExtensionPermissionScreenFactory)
}
}

View File

@ -1,16 +1,38 @@
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.*
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.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
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
@ -22,25 +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 com.twofasapp.resources.R
import com.twofasapp.design.compose.*
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.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.get
@Composable
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(),
router: SettingsRouter = get()
) {
val uiState = viewModel.uiState.collectAsState().value
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
var askForPermission by remember { mutableStateOf(false) }
Scaffold(
scaffoldState = scaffoldState,
@ -49,17 +117,28 @@ internal fun BrowserExtensionScreen(
if (uiState.isLoading) return@Scaffold
if (uiState.pairedBrowsers.isEmpty()) {
EmptyScreen(viewModel, padding)
EmptyScreen(router = router, padding = padding, onAddClick = { askForPermission = true })
} else {
ContentScreen(
viewModel = viewModel,
uiState = uiState,
router = router,
padding = padding
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 -> {
@ -73,32 +152,33 @@ internal fun BrowserExtensionScreen(
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
internal fun ContentScreen(
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 {
HeaderEntry(text = stringResource(id = R.string.browser__paired_devices_browser_title))
}
items(uiState.pairedBrowsers, key = { it.id }) {
SimpleEntry(
title = it.name,
SimpleEntry(title = it.name,
iconVisibleWhenNotSet = true,
subtitle = it.formatPairedAt(),
click = { router.navigate(SettingsDirections.BrowserDetails(extensionId = it.id)) }
)
click = { router.navigate(SettingsDirections.BrowserDetails(extensionId = it.id)) })
}
item {
Button(
onClick = { viewModel.onPairBrowserClick() },
shape = ButtonShape(),
modifier = Modifier.padding(start = 72.dp, top = 6.dp, bottom = 2.dp)
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())
}
@ -116,14 +196,30 @@ internal 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) {
@ -140,28 +236,27 @@ internal fun ContentScreen(
@Composable
internal fun EmptyScreen(
viewModel: BrowserExtensionViewModel,
router: SettingsRouter,
padding: PaddingValues,
onAddClick: () -> Unit,
) {
val activity = (LocalContext.current as? Activity)
ConstraintLayout(modifier = Modifier
.fillMaxSize()
.padding(padding)) {
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)
) {
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,
@ -184,28 +279,29 @@ internal fun EmptyScreen(
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)
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 = { viewModel.onPairBrowserClick() },
shape = ButtonShape(),
modifier = Modifier
.height(48.dp)
.constrainAs(pair) {
bottom.linkTo(parent.bottom, margin = 16.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
}) {
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 @@ internal 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,17 +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.navigation.SettingsDirections
import com.twofasapp.navigation.SettingsRouter
import com.twofasapp.permissions.CameraPermissionRequestFlow
import com.twofasapp.permissions.PermissionStatus
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
internal class BrowserExtensionViewModel(
private val dispatchers: Dispatchers,
private val settingsRouter: SettingsRouter,
private val cameraPermissionRequest: CameraPermissionRequestFlow,
private val observeMobileDeviceCase: ObserveMobileDeviceCase,
private val observePairedBrowsersCase: ObservePairedBrowsersCase,
private val updateMobileDeviceCase: UpdateMobileDeviceCase,
@ -51,24 +49,6 @@ internal class BrowserExtensionViewModel(
}
}
fun onPairBrowserClick() {
cameraPermissionRequest.execute()
.take(1)
.onEach {
when (it) {
PermissionStatus.GRANTED -> settingsRouter.navigate(SettingsDirections.PairingScan)
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,7 +1,14 @@
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.*
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
@ -16,11 +23,19 @@ 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.airbnb.lottie.compose.*
import com.twofasapp.resources.R
import com.twofasapp.design.compose.*
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
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.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
@ -28,6 +43,7 @@ internal fun PairingProgressScreen(
extensionId: String,
viewModel: PairingProgressViewModel = get(),
router: SettingsRouter = get(),
notificationManager: NotificationManager = get()
) {
val uiState = viewModel.uiState.collectAsState()
viewModel.pairBrowser(extensionId)
@ -42,7 +58,7 @@ internal fun PairingProgressScreen(
AnimatedContent(
condition = uiState.value.isPairing,
contentWhenTrue = { ProgressContent() },
contentWhenFalse = { ResultContent(uiState.value.isPairingSuccess, uiState.value.code, router) }
contentWhenFalse = { ResultContent(uiState.value.isPairingSuccess, uiState.value.code, router, notificationManager) }
)
}
}
@ -60,6 +76,7 @@ internal fun ResultContent(
isSuccess: Boolean,
code: Int? = null,
router: SettingsRouter,
notificationManager: NotificationManager,
) {
val image = if (isSuccess) R.drawable.browser_extension_success_image else R.drawable.browser_extension_error_image
@ -78,7 +95,15 @@ internal fun ResultContent(
val cta = if (isSuccess) R.string.commons__continue else R.string.browser__result_error_cta
val ctaAction: () -> Unit = if (isSuccess) {
{ router.navigate(SettingsDirections.GoBack) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (notificationManager.areNotificationsEnabled()) {
{ router.navigate(SettingsDirections.GoBack) }
} else {
{ router.navigate(SettingsDirections.Permission) }
}
} else {
{ router.navigate(SettingsDirections.GoBack) }
}
} else {
{ router.navigate(SettingsDirections.PairingScan) }
}

View File

@ -6,6 +6,7 @@ import com.twofasapp.base.dispatcher.Dispatchers
import com.twofasapp.browserextension.domain.PairBrowserCase
import com.twofasapp.browserextension.domain.RegisterMobileDeviceCase
import com.twofasapp.network.exception.BrowserAlreadyPairedException
import com.twofasapp.permissions.NotificationsPermissionRequestFlow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -16,6 +17,7 @@ internal 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

@ -27,6 +27,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

@ -1,5 +1,7 @@
package com.twofasapp.navigation
import android.os.Build
import androidx.annotation.RequiresApi
import com.twofasapp.navigation.base.Directions
sealed interface SettingsDirections : Directions {
@ -9,5 +11,6 @@ sealed interface SettingsDirections : Directions {
object BrowserExtension : SettingsDirections
class BrowserDetails(val extensionId: String) : SettingsDirections
object PairingScan : SettingsDirections
object Permission : SettingsDirections
class PairingProgress(val extensionId: String) : SettingsDirections
}

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>