mirror of
https://github.com/twofas/2fas-android.git
synced 2025-01-07 06:55:36 +01:00
Add notifications permission
This commit is contained in:
parent
7927edf99b
commit
43ad8d9114
@ -9,6 +9,8 @@
|
|||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="com.google.android.gms.permission.AD_ID"
|
android:name="com.google.android.gms.permission.AD_ID"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
@ -11,6 +11,7 @@ import com.twofasapp.environment.AppConfig
|
|||||||
import com.twofasapp.externalimport.domain.GoogleAuthenticatorImporter
|
import com.twofasapp.externalimport.domain.GoogleAuthenticatorImporter
|
||||||
import com.twofasapp.permissions.CameraPermissionRequest
|
import com.twofasapp.permissions.CameraPermissionRequest
|
||||||
import com.twofasapp.permissions.CameraPermissionRequestFlow
|
import com.twofasapp.permissions.CameraPermissionRequestFlow
|
||||||
|
import com.twofasapp.permissions.NotificationsPermissionRequestFlow
|
||||||
import com.twofasapp.services.analytics.AnalyticsServiceFirebase
|
import com.twofasapp.services.analytics.AnalyticsServiceFirebase
|
||||||
import com.twofasapp.services.googledrive.GoogleDriveService
|
import com.twofasapp.services.googledrive.GoogleDriveService
|
||||||
import com.twofasapp.services.googledrive.GoogleDriveServiceImpl
|
import com.twofasapp.services.googledrive.GoogleDriveServiceImpl
|
||||||
@ -43,6 +44,7 @@ val applicationModule = module {
|
|||||||
|
|
||||||
factory { CameraPermissionRequest(activityContext()) }
|
factory { CameraPermissionRequest(activityContext()) }
|
||||||
factory { CameraPermissionRequestFlow(activityContext()) }
|
factory { CameraPermissionRequestFlow(activityContext()) }
|
||||||
|
factory { NotificationsPermissionRequestFlow(activityContext()) }
|
||||||
|
|
||||||
single<Dispatchers> { AppDispatchers() }
|
single<Dispatchers> { AppDispatchers() }
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import androidx.navigation.NavHostController
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.twofasapp.browserextension.ui.browser.BrowserDetailsScreenFactory
|
import com.twofasapp.browserextension.ui.browser.BrowserDetailsScreenFactory
|
||||||
import com.twofasapp.browserextension.ui.main.BrowserExtensionScreenFactory
|
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.progress.PairingProgressScreenFactory
|
||||||
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
|
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
|
||||||
import com.twofasapp.settings.ui.main.SettingsMainScreenFactory
|
import com.twofasapp.settings.ui.main.SettingsMainScreenFactory
|
||||||
@ -19,6 +20,7 @@ class SettingsRouterImpl(
|
|||||||
private val pairingProgressScreenFactory: PairingProgressScreenFactory,
|
private val pairingProgressScreenFactory: PairingProgressScreenFactory,
|
||||||
private val pairingScanScreenFactory: PairingScanScreenFactory,
|
private val pairingScanScreenFactory: PairingScanScreenFactory,
|
||||||
private val browserDetailsScreenFactory: BrowserDetailsScreenFactory,
|
private val browserDetailsScreenFactory: BrowserDetailsScreenFactory,
|
||||||
|
private val browserExtensionPermissionScreenFactory: BrowserExtensionPermissionScreenFactory,
|
||||||
) : SettingsRouter() {
|
) : SettingsRouter() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -29,6 +31,7 @@ class SettingsRouterImpl(
|
|||||||
private const val BROWSER_EXTENSION = "browser_extension"
|
private const val BROWSER_EXTENSION = "browser_extension"
|
||||||
private const val BROWSER_DETAILS = "browser_details/{$ARG_EXTENSION_ID}"
|
private const val BROWSER_DETAILS = "browser_details/{$ARG_EXTENSION_ID}"
|
||||||
private const val PAIRING_SCAN = "pairing_scan"
|
private const val PAIRING_SCAN = "pairing_scan"
|
||||||
|
private const val PERMISSION = "permission"
|
||||||
private const val PAIRING_PROGRESS = "pairing_progress/{$ARG_EXTENSION_ID}"
|
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())
|
browserDetailsScreenFactory.create(it.arguments?.getString(ARG_EXTENSION_ID).orEmpty())
|
||||||
})
|
})
|
||||||
builder.composable(route = PAIRING_SCAN, content = { pairingScanScreenFactory.create() })
|
builder.composable(route = PAIRING_SCAN, content = { pairingScanScreenFactory.create() })
|
||||||
|
builder.composable(route = PERMISSION, content = { browserExtensionPermissionScreenFactory.create() })
|
||||||
builder.composable(route = PAIRING_PROGRESS, content = {
|
builder.composable(route = PAIRING_PROGRESS, content = {
|
||||||
pairingProgressScreenFactory.create(it.arguments?.getString(ARG_EXTENSION_ID).orEmpty())
|
pairingProgressScreenFactory.create(it.arguments?.getString(ARG_EXTENSION_ID).orEmpty())
|
||||||
})
|
})
|
||||||
@ -59,6 +63,7 @@ class SettingsRouterImpl(
|
|||||||
SettingsDirections.Theme -> navController.navigate(THEME)
|
SettingsDirections.Theme -> navController.navigate(THEME)
|
||||||
SettingsDirections.BrowserExtension -> navController.navigate(BROWSER_EXTENSION)
|
SettingsDirections.BrowserExtension -> navController.navigate(BROWSER_EXTENSION)
|
||||||
SettingsDirections.PairingScan -> navController.navigate(PAIRING_SCAN) { popUpTo(BROWSER_EXTENSION) }
|
SettingsDirections.PairingScan -> navController.navigate(PAIRING_SCAN) { popUpTo(BROWSER_EXTENSION) }
|
||||||
|
SettingsDirections.Permission -> navController.navigate(PERMISSION) { popUpTo(BROWSER_EXTENSION) }
|
||||||
|
|
||||||
is SettingsDirections.PairingProgress -> navController.navigate(
|
is SettingsDirections.PairingProgress -> navController.navigate(
|
||||||
PAIRING_PROGRESS.replace("{$ARG_EXTENSION_ID}", direction.extensionId)
|
PAIRING_PROGRESS.replace("{$ARG_EXTENSION_ID}", direction.extensionId)
|
||||||
|
@ -34,6 +34,7 @@ dependencies {
|
|||||||
implementation(libs.bundles.fastAdapter)
|
implementation(libs.bundles.fastAdapter)
|
||||||
implementation(libs.bundles.rxJava)
|
implementation(libs.bundles.rxJava)
|
||||||
implementation(libs.bundles.appCompat)
|
implementation(libs.bundles.appCompat)
|
||||||
|
implementation(libs.bundles.accompanist)
|
||||||
implementation(libs.lottie)
|
implementation(libs.lottie)
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
implementation(libs.kotlinCoroutines)
|
implementation(libs.kotlinCoroutines)
|
||||||
|
@ -4,7 +4,20 @@ import com.twofasapp.browserextension.data.BrowserExtensionLocalData
|
|||||||
import com.twofasapp.browserextension.data.BrowserExtensionLocalDataImpl
|
import com.twofasapp.browserextension.data.BrowserExtensionLocalDataImpl
|
||||||
import com.twofasapp.browserextension.data.BrowserExtensionRemoteData
|
import com.twofasapp.browserextension.data.BrowserExtensionRemoteData
|
||||||
import com.twofasapp.browserextension.data.BrowserExtensionRemoteDataImpl
|
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.BrowserExtensionRepository
|
||||||
import com.twofasapp.browserextension.domain.repository.BrowserExtensionRepositoryImpl
|
import com.twofasapp.browserextension.domain.repository.BrowserExtensionRepositoryImpl
|
||||||
import com.twofasapp.browserextension.notification.ShowBrowserExtensionRequestNotificationCaseImpl
|
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.browser.BrowserDetailsViewModel
|
||||||
import com.twofasapp.browserextension.ui.main.BrowserExtensionScreenFactory
|
import com.twofasapp.browserextension.ui.main.BrowserExtensionScreenFactory
|
||||||
import com.twofasapp.browserextension.ui.main.BrowserExtensionViewModel
|
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.PairingProgressScreenFactory
|
||||||
import com.twofasapp.browserextension.ui.pairing.progress.PairingProgressViewModel
|
import com.twofasapp.browserextension.ui.pairing.progress.PairingProgressViewModel
|
||||||
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
|
import com.twofasapp.browserextension.ui.pairing.scan.PairingScanScreenFactory
|
||||||
@ -46,6 +61,7 @@ class BrowserExtensionModule : KoinModule {
|
|||||||
singleOf(::DeletePairedBrowserCase)
|
singleOf(::DeletePairedBrowserCase)
|
||||||
|
|
||||||
viewModelOf(::BrowserExtensionViewModel)
|
viewModelOf(::BrowserExtensionViewModel)
|
||||||
|
viewModelOf(::BrowserExtensionPermissionViewModel)
|
||||||
viewModelOf(::PairingScanViewModel)
|
viewModelOf(::PairingScanViewModel)
|
||||||
viewModelOf(::PairingProgressViewModel)
|
viewModelOf(::PairingProgressViewModel)
|
||||||
viewModelOf(::BrowserExtensionRequestViewModel)
|
viewModelOf(::BrowserExtensionRequestViewModel)
|
||||||
@ -55,5 +71,6 @@ class BrowserExtensionModule : KoinModule {
|
|||||||
singleOf(::PairingProgressScreenFactory)
|
singleOf(::PairingProgressScreenFactory)
|
||||||
singleOf(::PairingScanScreenFactory)
|
singleOf(::PairingScanScreenFactory)
|
||||||
singleOf(::BrowserDetailsScreenFactory)
|
singleOf(::BrowserDetailsScreenFactory)
|
||||||
|
singleOf(::BrowserExtensionPermissionScreenFactory)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,16 +1,38 @@
|
|||||||
package com.twofasapp.browserextension.ui.main
|
package com.twofasapp.browserextension.ui.main
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
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.Image
|
||||||
import androidx.compose.foundation.clickable
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.*
|
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.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
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.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||||
import androidx.compose.ui.Modifier
|
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.style.TextAlign
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import com.twofasapp.resources.R
|
import androidx.core.content.ContextCompat
|
||||||
import com.twofasapp.design.compose.*
|
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.InputDialog
|
||||||
import com.twofasapp.design.compose.dialogs.RationaleDialog
|
import com.twofasapp.design.compose.dialogs.RationaleDialog
|
||||||
import com.twofasapp.extensions.openBrowserApp
|
import com.twofasapp.extensions.openBrowserApp
|
||||||
import com.twofasapp.navigation.SettingsDirections
|
import com.twofasapp.navigation.SettingsDirections
|
||||||
import com.twofasapp.navigation.SettingsRouter
|
import com.twofasapp.navigation.SettingsRouter
|
||||||
|
import com.twofasapp.resources.R
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.get
|
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
|
@Composable
|
||||||
internal fun BrowserExtensionScreen(
|
internal fun BrowserExtensionScreen(
|
||||||
viewModel: BrowserExtensionViewModel = get(),
|
viewModel: BrowserExtensionViewModel = get(),
|
||||||
router: SettingsRouter = get(),
|
router: SettingsRouter = get()
|
||||||
) {
|
) {
|
||||||
val uiState = viewModel.uiState.collectAsState().value
|
val uiState = viewModel.uiState.collectAsState().value
|
||||||
val scaffoldState = rememberScaffoldState()
|
val scaffoldState = rememberScaffoldState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
var askForPermission by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
@ -49,17 +117,28 @@ internal fun BrowserExtensionScreen(
|
|||||||
if (uiState.isLoading) return@Scaffold
|
if (uiState.isLoading) return@Scaffold
|
||||||
|
|
||||||
if (uiState.pairedBrowsers.isEmpty()) {
|
if (uiState.pairedBrowsers.isEmpty()) {
|
||||||
EmptyScreen(viewModel, padding)
|
EmptyScreen(router = router, padding = padding, onAddClick = { askForPermission = true })
|
||||||
} else {
|
} else {
|
||||||
ContentScreen(
|
ContentScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
router = router,
|
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 {
|
uiState.getMostRecentEvent()?.let {
|
||||||
when (it) {
|
when (it) {
|
||||||
is BrowserExtensionUiState.Event.ShowSnackbarError -> {
|
is BrowserExtensionUiState.Event.ShowSnackbarError -> {
|
||||||
@ -73,32 +152,33 @@ internal fun BrowserExtensionScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ContentScreen(
|
internal fun ContentScreen(
|
||||||
viewModel: BrowserExtensionViewModel,
|
viewModel: BrowserExtensionViewModel,
|
||||||
uiState: BrowserExtensionUiState,
|
uiState: BrowserExtensionUiState,
|
||||||
router: SettingsRouter,
|
router: SettingsRouter,
|
||||||
|
onAddClick: () -> Unit,
|
||||||
padding: PaddingValues,
|
padding: PaddingValues,
|
||||||
) {
|
) {
|
||||||
|
val activity = LocalContext.current as Activity
|
||||||
|
val permissionState = rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|
||||||
LazyColumn(modifier = Modifier.padding(padding)) {
|
LazyColumn(modifier = Modifier.padding(padding)) {
|
||||||
item {
|
item {
|
||||||
HeaderEntry(text = stringResource(id = R.string.browser__paired_devices_browser_title))
|
HeaderEntry(text = stringResource(id = R.string.browser__paired_devices_browser_title))
|
||||||
}
|
}
|
||||||
|
|
||||||
items(uiState.pairedBrowsers, key = { it.id }) {
|
items(uiState.pairedBrowsers, key = { it.id }) {
|
||||||
SimpleEntry(
|
SimpleEntry(title = it.name,
|
||||||
title = it.name,
|
|
||||||
iconVisibleWhenNotSet = true,
|
iconVisibleWhenNotSet = true,
|
||||||
subtitle = it.formatPairedAt(),
|
subtitle = it.formatPairedAt(),
|
||||||
click = { router.navigate(SettingsDirections.BrowserDetails(extensionId = it.id)) }
|
click = { router.navigate(SettingsDirections.BrowserDetails(extensionId = it.id)) })
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Button(
|
Button(
|
||||||
onClick = { viewModel.onPairBrowserClick() },
|
onClick = { onAddClick() }, shape = ButtonShape(), modifier = Modifier.padding(start = 72.dp, top = 6.dp, bottom = 2.dp)
|
||||||
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())
|
Text(text = stringResource(id = R.string.browser__add_new).uppercase(), color = ButtonTextColor())
|
||||||
}
|
}
|
||||||
@ -116,14 +196,30 @@ internal fun ContentScreen(
|
|||||||
iconEndClick = { viewModel.onEditDeviceClick() },
|
iconEndClick = { viewModel.onEditDeviceClick() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (uiState.showRationaleDialog) {
|
if (permissionState.status.isGranted.not()) {
|
||||||
RationaleDialog(
|
item {
|
||||||
title = stringResource(id = R.string.permissions__camera_permission),
|
Divider(Modifier.padding(top = 24.dp, bottom = 24.dp))
|
||||||
text = stringResource(id = R.string.permissions__camera_permission_description),
|
Text(
|
||||||
onDismiss = { viewModel.onRationaleDialogDismiss() }
|
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) {
|
if (uiState.showEditDeviceDialog) {
|
||||||
@ -140,28 +236,27 @@ internal fun ContentScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun EmptyScreen(
|
internal fun EmptyScreen(
|
||||||
viewModel: BrowserExtensionViewModel,
|
router: SettingsRouter,
|
||||||
padding: PaddingValues,
|
padding: PaddingValues,
|
||||||
|
onAddClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val activity = (LocalContext.current as? Activity)
|
val activity = (LocalContext.current as? Activity)
|
||||||
|
|
||||||
ConstraintLayout(modifier = Modifier
|
ConstraintLayout(
|
||||||
.fillMaxSize()
|
modifier = Modifier
|
||||||
.padding(padding)) {
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
) {
|
||||||
val (content, pair) = createRefs()
|
val (content, pair) = createRefs()
|
||||||
|
|
||||||
Column(
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
.constrainAs(content) {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
top.linkTo(parent.top)
|
||||||
modifier = Modifier
|
bottom.linkTo(pair.top)
|
||||||
.constrainAs(content) {
|
start.linkTo(parent.start)
|
||||||
top.linkTo(parent.top)
|
end.linkTo(parent.end)
|
||||||
bottom.linkTo(pair.top)
|
}
|
||||||
start.linkTo(parent.start)
|
.padding(vertical = 16.dp)) {
|
||||||
end.linkTo(parent.end)
|
|
||||||
}
|
|
||||||
.padding(vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.browser_extension_start_image),
|
painter = painterResource(id = R.drawable.browser_extension_start_image),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@ -184,28 +279,29 @@ internal fun EmptyScreen(
|
|||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(text = buildAnnotatedString {
|
Text(
|
||||||
append(stringResource(id = R.string.browser__more_info) + " ")
|
text = buildAnnotatedString {
|
||||||
withStyle(style = SpanStyle(MaterialTheme.colors.primary)) {
|
append(stringResource(id = R.string.browser__more_info) + " ")
|
||||||
append(stringResource(id = R.string.browser__more_info_link_title))
|
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)
|
}, style = MaterialTheme.typography.body2, modifier = Modifier
|
||||||
.align(CenterHorizontally)
|
.padding(horizontal = 16.dp)
|
||||||
.clickable {
|
.align(CenterHorizontally)
|
||||||
activity?.openBrowserApp(url = "https://2fas.com/be")
|
.clickable {
|
||||||
}, textAlign = TextAlign.Center)
|
activity?.openBrowserApp(url = "https://2fas.com/be")
|
||||||
|
}, textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(onClick = { viewModel.onPairBrowserClick() },
|
|
||||||
shape = ButtonShape(),
|
Button(onClick = { onAddClick() }, shape = ButtonShape(), modifier = Modifier
|
||||||
modifier = Modifier
|
.height(48.dp)
|
||||||
.height(48.dp)
|
.constrainAs(pair) {
|
||||||
.constrainAs(pair) {
|
bottom.linkTo(parent.bottom, margin = 16.dp)
|
||||||
bottom.linkTo(parent.bottom, margin = 16.dp)
|
start.linkTo(parent.start)
|
||||||
start.linkTo(parent.start)
|
end.linkTo(parent.end)
|
||||||
end.linkTo(parent.end)
|
}) {
|
||||||
}) {
|
|
||||||
Text(text = stringResource(id = R.string.browser__pair_with_web_browser).uppercase(), color = ButtonTextColor())
|
Text(text = stringResource(id = R.string.browser__pair_with_web_browser).uppercase(), color = ButtonTextColor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ internal data class BrowserExtensionUiState(
|
|||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val pairedBrowsers: List<PairedBrowser> = emptyList(),
|
val pairedBrowsers: List<PairedBrowser> = emptyList(),
|
||||||
val mobileDevice: MobileDevice? = null,
|
val mobileDevice: MobileDevice? = null,
|
||||||
val isCameraPermissionGranted: Boolean = false,
|
|
||||||
val showRationaleDialog: Boolean = false,
|
|
||||||
val showEditDeviceDialog: Boolean = false,
|
val showEditDeviceDialog: Boolean = false,
|
||||||
override val events: List<Event> = emptyList(),
|
override val events: List<Event> = emptyList(),
|
||||||
) : UiState<BrowserExtensionUiState, BrowserExtensionUiState.Event> {
|
) : UiState<BrowserExtensionUiState, BrowserExtensionUiState.Event> {
|
||||||
|
@ -7,17 +7,15 @@ import com.twofasapp.browserextension.domain.FetchPairedBrowsersCase
|
|||||||
import com.twofasapp.browserextension.domain.ObserveMobileDeviceCase
|
import com.twofasapp.browserextension.domain.ObserveMobileDeviceCase
|
||||||
import com.twofasapp.browserextension.domain.ObservePairedBrowsersCase
|
import com.twofasapp.browserextension.domain.ObservePairedBrowsersCase
|
||||||
import com.twofasapp.browserextension.domain.UpdateMobileDeviceCase
|
import com.twofasapp.browserextension.domain.UpdateMobileDeviceCase
|
||||||
import com.twofasapp.navigation.SettingsDirections
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import com.twofasapp.navigation.SettingsRouter
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import com.twofasapp.permissions.CameraPermissionRequestFlow
|
import kotlinx.coroutines.flow.combine
|
||||||
import com.twofasapp.permissions.PermissionStatus
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
internal class BrowserExtensionViewModel(
|
internal class BrowserExtensionViewModel(
|
||||||
private val dispatchers: Dispatchers,
|
private val dispatchers: Dispatchers,
|
||||||
private val settingsRouter: SettingsRouter,
|
|
||||||
private val cameraPermissionRequest: CameraPermissionRequestFlow,
|
|
||||||
private val observeMobileDeviceCase: ObserveMobileDeviceCase,
|
private val observeMobileDeviceCase: ObserveMobileDeviceCase,
|
||||||
private val observePairedBrowsersCase: ObservePairedBrowsersCase,
|
private val observePairedBrowsersCase: ObservePairedBrowsersCase,
|
||||||
private val updateMobileDeviceCase: UpdateMobileDeviceCase,
|
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() {
|
fun onEditDeviceClick() {
|
||||||
_uiState.update { it.copy(showEditDeviceDialog = true) }
|
_uiState.update { it.copy(showEditDeviceDialog = true) }
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.twofasapp.browserextension.ui.main.permission
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
class BrowserExtensionPermissionScreenFactory {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun create() {
|
||||||
|
BrowserExtensionPermissionScreen()
|
||||||
|
}
|
||||||
|
}
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,14 @@
|
|||||||
package com.twofasapp.browserextension.ui.pairing.progress
|
package com.twofasapp.browserextension.ui.pairing.progress
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.os.Build
|
||||||
import androidx.compose.foundation.Image
|
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.Button
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
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.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import com.airbnb.lottie.compose.*
|
import com.airbnb.lottie.compose.LottieAnimation
|
||||||
import com.twofasapp.resources.R
|
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||||
import com.twofasapp.design.compose.*
|
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.SettingsDirections
|
||||||
import com.twofasapp.navigation.SettingsRouter
|
import com.twofasapp.navigation.SettingsRouter
|
||||||
|
import com.twofasapp.resources.R
|
||||||
import org.koin.androidx.compose.get
|
import org.koin.androidx.compose.get
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -28,6 +43,7 @@ internal fun PairingProgressScreen(
|
|||||||
extensionId: String,
|
extensionId: String,
|
||||||
viewModel: PairingProgressViewModel = get(),
|
viewModel: PairingProgressViewModel = get(),
|
||||||
router: SettingsRouter = get(),
|
router: SettingsRouter = get(),
|
||||||
|
notificationManager: NotificationManager = get()
|
||||||
) {
|
) {
|
||||||
val uiState = viewModel.uiState.collectAsState()
|
val uiState = viewModel.uiState.collectAsState()
|
||||||
viewModel.pairBrowser(extensionId)
|
viewModel.pairBrowser(extensionId)
|
||||||
@ -42,7 +58,7 @@ internal fun PairingProgressScreen(
|
|||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
condition = uiState.value.isPairing,
|
condition = uiState.value.isPairing,
|
||||||
contentWhenTrue = { ProgressContent() },
|
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,
|
isSuccess: Boolean,
|
||||||
code: Int? = null,
|
code: Int? = null,
|
||||||
router: SettingsRouter,
|
router: SettingsRouter,
|
||||||
|
notificationManager: NotificationManager,
|
||||||
) {
|
) {
|
||||||
val image = if (isSuccess) R.drawable.browser_extension_success_image else R.drawable.browser_extension_error_image
|
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 cta = if (isSuccess) R.string.commons__continue else R.string.browser__result_error_cta
|
||||||
|
|
||||||
val ctaAction: () -> Unit = if (isSuccess) {
|
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 {
|
} else {
|
||||||
{ router.navigate(SettingsDirections.PairingScan) }
|
{ router.navigate(SettingsDirections.PairingScan) }
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.twofasapp.base.dispatcher.Dispatchers
|
|||||||
import com.twofasapp.browserextension.domain.PairBrowserCase
|
import com.twofasapp.browserextension.domain.PairBrowserCase
|
||||||
import com.twofasapp.browserextension.domain.RegisterMobileDeviceCase
|
import com.twofasapp.browserextension.domain.RegisterMobileDeviceCase
|
||||||
import com.twofasapp.network.exception.BrowserAlreadyPairedException
|
import com.twofasapp.network.exception.BrowserAlreadyPairedException
|
||||||
|
import com.twofasapp.permissions.NotificationsPermissionRequestFlow
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -16,6 +17,7 @@ internal class PairingProgressViewModel(
|
|||||||
private val dispatchers: Dispatchers,
|
private val dispatchers: Dispatchers,
|
||||||
private val registerMobileDeviceCase: RegisterMobileDeviceCase,
|
private val registerMobileDeviceCase: RegisterMobileDeviceCase,
|
||||||
private val pairBrowserCase: PairBrowserCase,
|
private val pairBrowserCase: PairBrowserCase,
|
||||||
|
private val notificationsPermissionRequestFlow: NotificationsPermissionRequestFlow,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(PairingProgressUiState())
|
private val _uiState = MutableStateFlow(PairingProgressUiState())
|
||||||
|
@ -27,6 +27,7 @@ accompanistPlaceholder = { module = "com.google.accompanist:accompanist-placehol
|
|||||||
accompanistSystemUi = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
|
accompanistSystemUi = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
|
||||||
accompanistPager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
|
accompanistPager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
|
||||||
accompanistPagerIndicators = { module = "com.google.accompanist:accompanist-pager-indicators", 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" }
|
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
|
||||||
coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
||||||
composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }
|
composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }
|
||||||
@ -153,6 +154,7 @@ accompanist = [
|
|||||||
"accompanistSystemUi",
|
"accompanistSystemUi",
|
||||||
"accompanistPager",
|
"accompanistPager",
|
||||||
"accompanistPagerIndicators",
|
"accompanistPagerIndicators",
|
||||||
|
"accompanistPermissions"
|
||||||
]
|
]
|
||||||
coil = [
|
coil = [
|
||||||
"coil",
|
"coil",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.twofasapp.navigation
|
package com.twofasapp.navigation
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import com.twofasapp.navigation.base.Directions
|
import com.twofasapp.navigation.base.Directions
|
||||||
|
|
||||||
sealed interface SettingsDirections : Directions {
|
sealed interface SettingsDirections : Directions {
|
||||||
@ -9,5 +11,6 @@ sealed interface SettingsDirections : Directions {
|
|||||||
object BrowserExtension : SettingsDirections
|
object BrowserExtension : SettingsDirections
|
||||||
class BrowserDetails(val extensionId: String) : SettingsDirections
|
class BrowserDetails(val extensionId: String) : SettingsDirections
|
||||||
object PairingScan : SettingsDirections
|
object PairingScan : SettingsDirections
|
||||||
|
object Permission : SettingsDirections
|
||||||
class PairingProgress(val extensionId: String) : SettingsDirections
|
class PairingProgress(val extensionId: String) : SettingsDirections
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
@ -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>
|
@ -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>
|
Loading…
Reference in New Issue
Block a user