mirror of
https://github.com/twofas/2fas-android.git
synced 2025-01-05 14:05:30 +01:00
Allow screenshots
This commit is contained in:
parent
6148592e2b
commit
1e7c48dd9e
@ -3,6 +3,7 @@ package com.twofasapp.features.backup
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.twofasapp.base.BaseActivityPresenter
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.databinding.ActivityBackupBinding
|
||||
@ -12,9 +13,11 @@ import com.twofasapp.extensions.navigationClicksThrottled
|
||||
import com.twofasapp.features.backup.settings.BackupSettingsFragment
|
||||
import com.twofasapp.features.backup.status.BackupStatusFragment
|
||||
import com.twofasapp.resources.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class BackupActivity : BaseActivityPresenter<ActivityBackupBinding>(), BackupContract.View, BackupStatusFragment.Listener, BackupSettingsFragment.Listener {
|
||||
class BackupActivity : BaseActivityPresenter<ActivityBackupBinding>(), BackupContract.View, BackupStatusFragment.Listener,
|
||||
BackupSettingsFragment.Listener {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_IS_OPENED_FROM_BACKUP_NOTICE = "isOpenedFromBackupNotice"
|
||||
@ -27,7 +30,13 @@ class BackupActivity : BaseActivityPresenter<ActivityBackupBinding>(), BackupCon
|
||||
ThemeState.applyTheme(settingsRepository.getAppSettings().selectedTheme)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(ActivityBackupBinding::inflate)
|
||||
setPresenter(presenter)
|
||||
|
||||
|
@ -5,7 +5,9 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.twofasapp.base.BaseActivityPresenter
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.databinding.ActivityImportBackupBinding
|
||||
import com.twofasapp.design.dialogs.InfoDialog
|
||||
import com.twofasapp.design.dialogs.SimpleInputDialog
|
||||
@ -15,6 +17,8 @@ import com.twofasapp.extensions.makeVisible
|
||||
import com.twofasapp.extensions.makeWindowSecure
|
||||
import com.twofasapp.extensions.navigationClicksThrottled
|
||||
import com.twofasapp.extensions.toastLong
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class ImportBackupActivity : BaseActivityPresenter<ActivityImportBackupBinding>(), ImportBackupContract.View {
|
||||
|
||||
@ -23,10 +27,15 @@ class ImportBackupActivity : BaseActivityPresenter<ActivityImportBackupBinding>(
|
||||
}
|
||||
|
||||
private val presenter: ImportBackupContract.Presenter by injectThis()
|
||||
private val settingsRepository: SettingsRepository by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
setContentView(ActivityImportBackupBinding::inflate)
|
||||
setPresenter(presenter)
|
||||
|
||||
|
@ -49,7 +49,11 @@ class MainActivity : AppCompatActivity(), AuthAware {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeState.applyTheme(settingsRepository.getAppSettings().selectedTheme)
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
|
||||
setContent { MainScreen() }
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import com.mikepenz.fastadapter.IItem
|
||||
import com.mikepenz.fastadapter.adapters.FastItemAdapter
|
||||
@ -21,6 +22,7 @@ import com.twofasapp.extensions.makeWindowSecure
|
||||
import com.twofasapp.extensions.navigationClicksThrottled
|
||||
import com.twofasapp.views.ModelDiffUtilCallback
|
||||
import com.twofasapp.widgets.broadcast.WidgetBroadcaster
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class WidgetSettingsActivity : BaseActivityPresenter<ActivityWidgetSettingsBinding>(), WidgetSettingsContract.View, AuthAware {
|
||||
@ -36,7 +38,11 @@ class WidgetSettingsActivity : BaseActivityPresenter<ActivityWidgetSettingsBindi
|
||||
|
||||
authTracker.onWidgetSettingsScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
setContentView(ActivityWidgetSettingsBinding::inflate)
|
||||
setPresenter(presenter)
|
||||
viewBinding.recycler.adapter = fastAdapter
|
||||
|
@ -5,32 +5,40 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.twofasapp.backup.databinding.ActivityExportBackupBinding
|
||||
import com.twofasapp.base.BaseActivityPresenter
|
||||
import com.twofasapp.common.environment.AppBuild
|
||||
import com.twofasapp.core.RequestCodes
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.design.dialogs.InfoDialog
|
||||
import com.twofasapp.extensions.clicksThrottled
|
||||
import com.twofasapp.extensions.makeWindowSecure
|
||||
import com.twofasapp.extensions.navigationClicksThrottled
|
||||
import com.twofasapp.extensions.toastLong
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class ExportBackupActivity : BaseActivityPresenter<ActivityExportBackupBinding>(), ExportBackupContract.View, ExportBackupPasswordDialog.Listener {
|
||||
class ExportBackupActivity : BaseActivityPresenter<ActivityExportBackupBinding>(), ExportBackupContract.View,
|
||||
ExportBackupPasswordDialog.Listener {
|
||||
|
||||
companion object {
|
||||
private const val EXPORT_FILE_PICKER = 48453
|
||||
}
|
||||
|
||||
private val presenter: ExportBackupContract.Presenter by injectThis()
|
||||
private val analyticsService: com.twofasapp.core.analytics.AnalyticsService by inject()
|
||||
private val settingsRepository: SettingsRepository by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
setContentView(ActivityExportBackupBinding::inflate)
|
||||
setPresenter(presenter)
|
||||
}
|
||||
@ -67,14 +75,14 @@ class ExportBackupActivity : BaseActivityPresenter<ActivityExportBackupBinding>(
|
||||
try {
|
||||
startActivityForResult(intent, EXPORT_FILE_PICKER)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
|
||||
|
||||
InfoDialog(
|
||||
context = this,
|
||||
title = "Error",
|
||||
msg = "Could not find system file provider.\n\nIf you removed default documents application you need to restore it in order to make the export work."
|
||||
).show()
|
||||
} catch (e: Exception) {
|
||||
|
||||
|
||||
toastLong("System error! Could not launch file provider!")
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,12 @@ fun MainAppTheme(
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(this, false)
|
||||
|
||||
if (BuildConfig.BUILD_TYPE.equals("release", true)) {
|
||||
setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
}
|
||||
// if (BuildConfig.BUILD_TYPE.equals("release", true)) {
|
||||
// setFlags(
|
||||
// WindowManager.LayoutParams.FLAG_SECURE,
|
||||
// WindowManager.LayoutParams.FLAG_SECURE
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,4 +60,5 @@ object TwIcons {
|
||||
val Close @Composable get() = painterResource(R.drawable.ic_close)
|
||||
val ListStyle @Composable get() = painterResource(R.drawable.ic_list_style)
|
||||
val IncrementHotp @Composable get() = painterResource(R.drawable.ic_increment_hotp)
|
||||
val Screenshot @Composable get() = painterResource(R.drawable.ic_screenshot)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M182.48,278.3L182.48,166.78q0,-44.31 30.85,-75.15 30.85,-30.85 75.15,-30.85L400,60.78v97.52L280,158.3v120h-97.52ZM288.48,899.22q-44.31,0 -75.15,-30.85 -30.85,-30.85 -30.85,-75.15L182.48,681.7L280,681.7v120h120v97.52L288.48,899.22ZM680,278.3v-120L560,158.3v-97.52h111.52q44.31,0 75.15,30.85 30.85,30.85 30.85,75.15v111.52L680,278.3ZM560,899.22v-97.52h120v-120h97.52v111.52q0,44.31 -30.85,75.15 -30.85,30.85 -75.15,30.85L560,899.22Z"/>
|
||||
</vector>
|
@ -148,14 +148,39 @@ class Strings(c: Context) {
|
||||
|
||||
val tokenRequestTitle = c.getString(R.string.browser__2fa_token_request_title)
|
||||
val tokenRequestBody = c.getString(R.string.browser__2fa_token_request_content)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
// val browserExt = c.getString(R.string.)
|
||||
|
||||
val addTitle = c.getString(R.string.tokens__add_title)
|
||||
val addDescription = c.getString(R.string.tokens__add_description)
|
||||
val addOtherMethods = c.getString(R.string.tokens__add_other_methods)
|
||||
val addEnterManual = c.getString(R.string.tokens__add_enter_manual)
|
||||
val addFromGallery = c.getString(R.string.tokens__add_from_gallery)
|
||||
val addSuccessTitle = c.getString(R.string.tokens__add_success_title)
|
||||
val addSuccessDescription = c.getString(R.string.tokens__add_success_description)
|
||||
val addManualTitle = c.getString(R.string.tokens__add_manual_title)
|
||||
val addManualDescription = c.getString(R.string.tokens__add_manual_description)
|
||||
val addManualServiceName = c.getString(R.string.tokens__add_manual_service_name)
|
||||
val addManualServiceKey = c.getString(R.string.tokens__add_manual_service_key)
|
||||
val addManualOther = c.getString(R.string.tokens__add_manual_other)
|
||||
val addManualOtherOptional = c.getString(R.string.tokens__add_manual_other_optional)
|
||||
val addManualDoneCta = c.getString(R.string.tokens__add_manual_done_cta)
|
||||
val addManualHelpCta = c.getString(R.string.tokens__add_manual_help_cta)
|
||||
val addManualAdvanced = c.getString(R.string.tokens__add_manual_advanced)
|
||||
val addManualAdvancedDescription = c.getString(R.string.tokens__add_manual_advanced_description)
|
||||
val addManualAdditionalInfo = c.getString(R.string.tokens__add_manual_additional_info)
|
||||
val addManualAlgorithm = c.getString(R.string.tokens__algorithm)
|
||||
val addManualRefreshTime = c.getString(R.string.tokens__refresh_time)
|
||||
val addManualInitialCounter = c.getString(R.string.tokens__initial_counter)
|
||||
val addManualDigits = c.getString(R.string.tokens__number_of_digits)
|
||||
|
||||
val addScanInvalidQrTitle = c.getString(R.string.tokens__qr_does_not_work)
|
||||
val addScanInvalidQrBody = c.getString(R.string.tokens__qr_point_and_scan_again)
|
||||
val addScanInvalidQrCta = c.getString(R.string.tokens__try_again)
|
||||
|
||||
val addScanServiceExistsTitle = c.getString(R.string.commons__warning)
|
||||
val addScanServiceExistsBody = c.getString(R.string.tokens__service_already_exists)
|
||||
val addScanServiceExistsPositiveCta = c.getString(R.string.commons__yes)
|
||||
val addScanServiceExistsNegativeCta = c.getString(R.string.commons__no)
|
||||
|
||||
|
||||
// val add = c.getString(R.string.tokens__add_)
|
||||
}
|
@ -16,4 +16,6 @@ dependencies {
|
||||
|
||||
implementation(libs.kotlinCoroutines)
|
||||
implementation(libs.kotlinSerialization)
|
||||
implementation(libs.workManager)
|
||||
implementation(libs.timber)
|
||||
}
|
@ -16,4 +16,5 @@ interface SettingsRepository {
|
||||
suspend fun setAutoFocusSearch(autoFocusSearch: Boolean)
|
||||
suspend fun setShowBackupNotice(showBackupNotice: Boolean)
|
||||
suspend fun setSendCrashLogs(sendCrashLogs: Boolean)
|
||||
suspend fun setAllowScreenshots(allow: Boolean)
|
||||
}
|
@ -63,4 +63,10 @@ internal class SettingsRepositoryImpl(
|
||||
local.setSendCrashLogs(sendCrashLogs)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAllowScreenshots(allow: Boolean) {
|
||||
withContext(dispatchers.io) {
|
||||
local.setAllowScreenshots(allow)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.data.session.SettingsRepositoryImpl
|
||||
import com.twofasapp.data.session.local.SessionLocalSource
|
||||
import com.twofasapp.data.session.local.SettingsLocalSource
|
||||
import com.twofasapp.data.session.work.DisableScreenshotsWorkDispatcher
|
||||
import com.twofasapp.di.KoinModule
|
||||
import org.koin.core.module.dsl.bind
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
@ -18,5 +19,7 @@ class DataSessionModule : KoinModule {
|
||||
|
||||
singleOf(::SettingsLocalSource)
|
||||
singleOf(::SettingsRepositoryImpl) { bind<SettingsRepository>() }
|
||||
|
||||
singleOf(::DisableScreenshotsWorkDispatcher)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ data class AppSettings(
|
||||
val autoFocusSearch: Boolean = false,
|
||||
val showBackupNotice: Boolean = false,
|
||||
val sendCrashLogs: Boolean = false,
|
||||
val allowScreenshots: Boolean = false,
|
||||
val selectedTheme: SelectedTheme = SelectedTheme.Auto,
|
||||
val servicesStyle: ServicesStyle = ServicesStyle.Default,
|
||||
val servicesSort: ServicesSort = ServicesSort.Manual,
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.twofasapp.data.session.local
|
||||
|
||||
import com.twofasapp.common.environment.AppBuild
|
||||
import com.twofasapp.common.environment.BuildVariant
|
||||
import com.twofasapp.data.session.domain.AppSettings
|
||||
import com.twofasapp.data.session.domain.SelectedTheme
|
||||
import com.twofasapp.data.session.domain.ServicesSort
|
||||
@ -10,7 +12,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
internal class SettingsLocalSource(
|
||||
private val preferences: PlainPreferences
|
||||
private val preferences: PlainPreferences,
|
||||
private val appBuild: AppBuild,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@ -21,7 +24,7 @@ internal class SettingsLocalSource(
|
||||
private const val KeyServicesSort = "servicesSort"
|
||||
private const val KeyAutoFocusSearch = "autoFocusSearch"
|
||||
private const val KeySendCrashLogs = "sendCrashLogs"
|
||||
|
||||
private const val KeyAllowScreenshots = "allowScreenshots"
|
||||
}
|
||||
|
||||
private val appSettingsFlow: MutableStateFlow<AppSettings> by lazy {
|
||||
@ -38,6 +41,11 @@ internal class SettingsLocalSource(
|
||||
autoFocusSearch = preferences.getBoolean(KeyAutoFocusSearch) ?: false,
|
||||
showBackupNotice = preferences.getBoolean(KeyShowBackupNotice) ?: true,
|
||||
sendCrashLogs = preferences.getBoolean(KeySendCrashLogs) ?: true,
|
||||
allowScreenshots = preferences.getBoolean(KeyAllowScreenshots) ?: when (appBuild.buildVariant) {
|
||||
BuildVariant.Release -> false
|
||||
BuildVariant.ReleaseLocal -> true
|
||||
BuildVariant.Debug -> true
|
||||
},
|
||||
selectedTheme = preferences.getString(KeySelectedTheme)?.let { SelectedTheme.valueOf(it) } ?: SelectedTheme.Auto,
|
||||
servicesStyle = preferences.getString(KeyServicesStyle)?.let { ServicesStyle.valueOf(it) } ?: ServicesStyle.Default,
|
||||
servicesSort = preferences.getString(KeyServicesSort)?.let { ServicesSort.valueOf(it) } ?: ServicesSort.Manual,
|
||||
@ -78,4 +86,9 @@ internal class SettingsLocalSource(
|
||||
appSettingsFlow.update { it.copy(sendCrashLogs = sendCrashLogs) }
|
||||
preferences.putBoolean(KeySendCrashLogs, sendCrashLogs)
|
||||
}
|
||||
|
||||
fun setAllowScreenshots(allow: Boolean) {
|
||||
appSettingsFlow.update { it.copy(allowScreenshots = allow) }
|
||||
preferences.putBoolean(KeyAllowScreenshots, allow)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.twofasapp.data.session.work
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class DisableScreenshotsWork(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters,
|
||||
) : CoroutineWorker(appContext, workerParams), KoinComponent {
|
||||
|
||||
private val settingsRepository: SettingsRepository by inject()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
settingsRepository.setAllowScreenshots(false)
|
||||
return Result.success()
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.twofasapp.data.session.work
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.twofasapp.di.WorkDispatcher
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class DisableScreenshotsWorkDispatcher(
|
||||
private val context: Context
|
||||
) : WorkDispatcher {
|
||||
|
||||
fun dispatch() {
|
||||
val request = OneTimeWorkRequestBuilder<DisableScreenshotsWork>()
|
||||
.setInitialDelay(5, TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork("DisableScreenshotsWork", ExistingWorkPolicy.REPLACE, request)
|
||||
}
|
||||
}
|
@ -2,32 +2,22 @@ package com.twofasapp.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.twofasapp.extensions.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun AppCompatActivity.replaceFragment(layoutRes: Int, fragment: Fragment) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(layoutRes, fragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
||||
fun Activity.makeWindowSecure() {
|
||||
if (BuildConfig.BUILD_TYPE.equals("release", true)) {
|
||||
window?.setFlags(
|
||||
fun Activity.makeWindowSecure(allow: Boolean) {
|
||||
if (allow) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun doNothing() = Unit
|
||||
|
||||
fun String.splitIntoLetters(): String = replace(".".toRegex(), "$0 ")
|
||||
@ -42,19 +32,6 @@ fun String.removeWhiteCharacters(): String {
|
||||
return replace(" ", "")
|
||||
}
|
||||
|
||||
val Activity.screenRectPx: Rect
|
||||
get() = Resources.getSystem().displayMetrics.run { Rect(0, 0, widthPixels, heightPixels) }
|
||||
|
||||
val Activity.screenRectDp: RectF
|
||||
get() = Resources.getSystem().displayMetrics.run {
|
||||
RectF(
|
||||
0f,
|
||||
0f,
|
||||
widthPixels.px2dp,
|
||||
heightPixels.px2dp
|
||||
)
|
||||
}
|
||||
|
||||
val Number.px2dp: Float
|
||||
get() = this.toFloat() / Resources.getSystem().displayMetrics.density
|
||||
|
||||
|
@ -21,7 +21,7 @@ internal class PermissionActivity : FragmentActivity(), ActivityCompat.OnRequest
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
makeWindowSecure(allow = true)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
||||
|
||||
val permission = intent.getStringExtra(PERMISSION_KEY)
|
||||
|
@ -5,12 +5,14 @@ import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.twofasapp.base.AuthTracker
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.design.theme.ThemeState
|
||||
import com.twofasapp.designsystem.MainAppTheme
|
||||
import com.twofasapp.extensions.makeWindowSecure
|
||||
import com.twofasapp.resources.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class LockActivity : AppCompatActivity() {
|
||||
@ -26,7 +28,11 @@ class LockActivity : AppCompatActivity() {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
makeWindowSecure()
|
||||
lifecycleScope.launch {
|
||||
settingsRepository.observeAppSettings().collect {
|
||||
makeWindowSecure(allow = it.allowScreenshots)
|
||||
}
|
||||
}
|
||||
|
||||
setContent {
|
||||
MainAppTheme {
|
||||
|
@ -24,8 +24,10 @@ import com.twofasapp.design.compose.HeaderEntry
|
||||
import com.twofasapp.design.compose.SimpleEntry
|
||||
import com.twofasapp.design.compose.SubtitleGravity
|
||||
import com.twofasapp.design.compose.SwitchEntry
|
||||
import com.twofasapp.designsystem.TwIcons
|
||||
import com.twofasapp.designsystem.TwTheme
|
||||
import com.twofasapp.designsystem.common.TwTopAppBar
|
||||
import com.twofasapp.designsystem.dialog.ConfirmDialog
|
||||
import com.twofasapp.designsystem.dialog.ListRadioDialog
|
||||
import com.twofasapp.resources.R
|
||||
import com.twofasapp.security.domain.model.LockMethod
|
||||
@ -47,6 +49,7 @@ internal fun SecurityScreen(
|
||||
var showTrailsDialog by remember { mutableStateOf(false) }
|
||||
var showTimeoutDialog by remember { mutableStateOf(false) }
|
||||
var showBiometricDialog by remember { mutableStateOf(false) }
|
||||
var showScreenshotsConfirmDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -70,6 +73,7 @@ internal fun SecurityScreen(
|
||||
item {
|
||||
SwitchEntry(
|
||||
title = stringResource(id = R.string.settings__option_fingerprint),
|
||||
subtitle = stringResource(id = R.string.settings__option_fingerprint_description),
|
||||
icon = painterResource(id = R.drawable.ic_fingerprint_old),
|
||||
isChecked = false,
|
||||
isEnabled = false,
|
||||
@ -77,11 +81,21 @@ internal fun SecurityScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item { Divider(color = TwTheme.color.divider, modifier = Modifier.padding(vertical = 8.dp)) }
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings__option_fingerprint_description),
|
||||
modifier = Modifier.padding(start = 24.dp, end = 16.dp, top = 8.dp),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(color = TwTheme.color.onSurfaceSecondary)
|
||||
SwitchEntry(
|
||||
title = stringResource(id = R.string.settings__option_screenshots),
|
||||
subtitle = stringResource(id = R.string.settings__option_screenshots_description),
|
||||
icon = TwIcons.Screenshot,
|
||||
isChecked = uiState.allowScreenshots,
|
||||
switch = { isChecked ->
|
||||
if (isChecked) {
|
||||
showScreenshotsConfirmDialog = true
|
||||
} else {
|
||||
viewModel.toggleScreenshots()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -168,6 +182,24 @@ internal fun SecurityScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item { Divider(color = TwTheme.color.divider, modifier = Modifier.padding(vertical = 8.dp)) }
|
||||
|
||||
item {
|
||||
SwitchEntry(
|
||||
title = stringResource(id = R.string.settings__option_screenshots),
|
||||
subtitle = stringResource(id = R.string.settings__option_screenshots_description),
|
||||
icon = TwIcons.Screenshot,
|
||||
isChecked = uiState.allowScreenshots,
|
||||
switch = { isChecked ->
|
||||
if (isChecked) {
|
||||
showScreenshotsConfirmDialog = true
|
||||
} else {
|
||||
viewModel.toggleScreenshots()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item { Divider(color = TwTheme.color.divider) }
|
||||
}
|
||||
}
|
||||
@ -210,5 +242,16 @@ internal fun SecurityScreen(
|
||||
onDismiss = { showBiometricDialog = false },
|
||||
).show()
|
||||
}
|
||||
|
||||
if (showScreenshotsConfirmDialog) {
|
||||
ConfirmDialog(
|
||||
onDismissRequest = { showScreenshotsConfirmDialog = false },
|
||||
title = stringResource(id = R.string.settings__option_screenshots_confirm_title),
|
||||
body = stringResource(id = R.string.settings__option_screenshots_confirm_description),
|
||||
negative = stringResource(id = R.string.commons__no),
|
||||
positive = stringResource(id = R.string.commons__yes),
|
||||
onConfirm = { viewModel.toggleScreenshots() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,4 +10,5 @@ internal data class SecurityUiState(
|
||||
val pinTrials: PinTrials = PinTrials.Trials3,
|
||||
val pinTimeout: PinTimeout = PinTimeout.Timeout5,
|
||||
val pinDigits: PinDigits = PinDigits.Code4,
|
||||
val allowScreenshots: Boolean = false,
|
||||
)
|
@ -3,6 +3,9 @@ package com.twofasapp.security.ui.security
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.twofasapp.base.BaseViewModel
|
||||
import com.twofasapp.base.dispatcher.Dispatchers
|
||||
import com.twofasapp.common.ktx.launchScoped
|
||||
import com.twofasapp.data.session.SettingsRepository
|
||||
import com.twofasapp.data.session.work.DisableScreenshotsWorkDispatcher
|
||||
import com.twofasapp.security.domain.EditLockMethodCase
|
||||
import com.twofasapp.security.domain.EditPinOptionsCase
|
||||
import com.twofasapp.security.domain.ObserveLockMethodCase
|
||||
@ -12,7 +15,6 @@ import com.twofasapp.security.domain.model.PinOptions
|
||||
import com.twofasapp.security.domain.model.PinTimeout
|
||||
import com.twofasapp.security.domain.model.PinTrials
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@ -23,10 +25,11 @@ internal class SecurityViewModel(
|
||||
private val observePinOptionsCase: ObservePinOptionsCase,
|
||||
private val editPinOptionsCase: EditPinOptionsCase,
|
||||
private val editLockMethodCase: EditLockMethodCase,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val disableScreenshotsWorkDispatcher: DisableScreenshotsWorkDispatcher,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(SecurityUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val uiState = MutableStateFlow(SecurityUiState())
|
||||
|
||||
init {
|
||||
viewModelScope.launch(dispatchers.io()) {
|
||||
@ -35,7 +38,7 @@ internal class SecurityViewModel(
|
||||
observePinOptionsCase.invoke()
|
||||
) { a, b -> Pair(a, b) }
|
||||
.collect { (lockMethod, pinOptions) ->
|
||||
_uiState.update { state ->
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
lockMethod = lockMethod,
|
||||
pinTrials = pinOptions.trials,
|
||||
@ -45,6 +48,12 @@ internal class SecurityViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launchScoped {
|
||||
settingsRepository.observeAppSettings().collect { appSettings ->
|
||||
uiState.update { it.copy(allowScreenshots = appSettings.allowScreenshots) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePinTrails(pinTrials: PinTrials) {
|
||||
@ -62,6 +71,18 @@ internal class SecurityViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleScreenshots() {
|
||||
val isAllowed = uiState.value.allowScreenshots.not()
|
||||
|
||||
launchScoped {
|
||||
settingsRepository.setAllowScreenshots(isAllowed)
|
||||
}
|
||||
|
||||
if (isAllowed) {
|
||||
disableScreenshotsWorkDispatcher.dispatch()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePinOptions(action: (PinOptions) -> PinOptions) {
|
||||
viewModelScope.launch(dispatchers.io()) {
|
||||
editPinOptionsCase(
|
||||
|
Loading…
Reference in New Issue
Block a user