copied xml code to compose module (#305)

This commit is contained in:
Yogesh Choudhary Paliyal
2023-01-19 00:04:59 +05:30
committed by GitHub
parent b075fec701
commit 34613b29fa
175 changed files with 6216 additions and 319 deletions

View File

@@ -1,6 +1,10 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
id("dagger.hilt.android.plugin")
}
android {
@@ -35,7 +39,27 @@ android {
}
buildFeatures {
compose true
viewBinding = true
dataBinding = true
}
flavorDimensions "default"
productFlavors {
production {
}
staging {
applicationIdSuffix ".staging"
}
}
sourceSets {
main {
res {
srcDirs 'src\\main\\res', 'src\\staging\\res'
}
}
}
composeOptions {
kotlinCompilerExtensionVersion = "1.2.0-beta01"
}
@@ -67,4 +91,46 @@ dependencies {
debugImplementation "androidx.compose.ui:ui-tooling:1.3.3"
implementation 'androidx.compose.material3:material3:1.1.0-alpha04'
// XML Libraries
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
kapt "androidx.room:room-compiler:$room_version"
implementation 'com.yogeshpaliyal:universal-adapter:3.0.1'
// dependency injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
implementation("androidx.hilt:hilt-work:1.0.0")
// When using Kotlin.
kapt("androidx.hilt:hilt-compiler:1.0.0")
// zxing library
// implementation "com.googl.ezxing:android-core:3.4.1"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
// For instrumented tests.
androidTestImplementation("com.google.dagger:hilt-android-testing:2.44.2")
// ...with Kotlin.
kaptAndroidTest("com.google.dagger:hilt-android-compiler:$hilt_version")
// For Robolectric tests.
testImplementation("com.google.dagger:hilt-android-testing:2.44")
// ...with Kotlin.
kaptTest("com.google.dagger:hilt-android-compiler:$hilt_version")
}

View File

@@ -1,25 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.yogeshpaliyal.keypasscompose">
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.KeyPass">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.KeyPass.NoActionBar">
android:name=".ui.CrashActivity"
android:exported="false" />
<activity
android:name=".ui.addTOTP.AddTOTPActivity"
android:exported="false" />
<activity
android:name=".ui.addTOTP.ScannerActivity"
android:exported="false" />
<activity
android:name=".ui.backup.BackupActivity"
android:exported="false" />
<activity
android:name=".ui.generate.GeneratePasswordActivity"
android:exported="true" />
<activity
android:name=".ui.auth.AuthenticationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:exported="false"
android:screenOrientation="portrait"
tools:replace="screenOrientation" />
<activity
android:name=".ui.nav.DashboardActivity"
android:exported="false"
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".ui.detail.DetailActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> <!-- If you want to disable android.startup completely. -->
</application>
</manifest>

View File

@@ -1,89 +0,0 @@
package com.yogeshpaliyal.keypasscompose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.FabPosition
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.yogeshpaliyal.keypasscompose.ui.theme.KeyPassTheme
import com.yogeshpaliyal.keypasscompose.ui.theme.Material3BottomAppBar
import com.yogeshpaliyal.keypasscompose.ui.theme.Material3Scaffold
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KeyPassTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colorScheme.background) {
Material3Scaffold(
bottomBar = {
Material3BottomAppBar(cutoutShape = RoundedCornerShape(50)) {
IconButton(
onClick = {
/* doSomething() */
}
) {
Icon(Icons.Filled.Menu, "")
}
Spacer(Modifier.weight(1f, true))
IconButton(
onClick = {
/* doSomething() */
}
) {
Icon(Icons.Filled.Star, "")
}
}
},
floatingActionButton = {
FloatingActionButton(
onClick = {
},
contentColor = Color.White,
shape = RoundedCornerShape(50)
) {
Icon(Icons.Filled.Add, "")
}
},
floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true
) {
}
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
KeyPassTheme {
Greeting("Android")
}
}

View File

@@ -0,0 +1,20 @@
package com.yogeshpaliyal.keypasscompose
import android.content.Intent
import com.yogeshpaliyal.common.CommonMyApplication
import com.yogeshpaliyal.keypasscompose.ui.CrashActivity
import dagger.hilt.android.HiltAndroidApp
/*
* @author Yogesh Paliyal
* yogeshpaliyal.foss@gmail.com
* https://techpaliyal.com
* created on 22-01-2021 22:41
*/
@HiltAndroidApp
class MyApplication : CommonMyApplication() {
override fun getCrashActivityIntent(throwable: Throwable): Intent {
return CrashActivity.getIntent(this, throwable.localizedMessage)
}
}

View File

@@ -0,0 +1,47 @@
package com.yogeshpaliyal.keypasscompose.custom_views
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.graphics.RectF
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.shape.ShapeAppearancePathProvider
import com.yogeshpaliyal.keypasscompose.R
/**
* A Card view that clips the content of any shape, this should be done upstream in card,
* working around it for now.
*/
class MaskedCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.attr.materialCardViewStyle,
) : MaterialCardView(context, attrs, defStyle) {
@SuppressLint("RestrictedApi")
private val pathProvider = ShapeAppearancePathProvider()
private val path: Path = Path()
private val shapeAppearance: ShapeAppearanceModel = ShapeAppearanceModel.builder(
context,
attrs,
defStyle,
R.style.Widget_MaterialComponents_CardView
).build()
private val rectF = RectF(0f, 0f, 0f, 0f)
override fun onDraw(canvas: Canvas) {
canvas.clipPath(path)
super.onDraw(canvas)
}
@SuppressLint("RestrictedApi")
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
rectF.right = w.toFloat()
rectF.bottom = h.toFloat()
pathProvider.calculatePath(shapeAppearance, 1f, rectF, path)
super.onSizeChanged(w, h, oldw, oldh)
}
}

View File

@@ -0,0 +1,36 @@
package com.yogeshpaliyal.keypasscompose.data
import com.google.gson.Gson
import com.yogeshpaliyal.common.constants.AccountType
import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.universalAdapter.listener.UniversalViewType
import com.yogeshpaliyal.universalAdapter.model.BaseDiffUtil
class MyAccountModel : AccountModel(), BaseDiffUtil, UniversalViewType {
override fun getDiffId(): Any? {
return id
}
override fun getDiffBody(): Any? {
return if (type == AccountType.TOTP) {
super.getDiffBody()
} else {
Gson().toJson(this)
}
}
override fun getLayoutId(): Int = if (type == AccountType.TOTP) R.layout.item_totp else R.layout.item_accounts
fun map(accountModel: AccountModel) {
this.id = accountModel.id
this.title = accountModel.title
this.uniqueId = accountModel.uniqueId
this.username = accountModel.username
this.password = accountModel.password
this.site = accountModel.site
this.notes = accountModel.notes
this.tags = accountModel.tags
this.type = accountModel.type
}
}

View File

@@ -0,0 +1,14 @@
package com.yogeshpaliyal.keypasscompose.listener
import android.view.View
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 09:00
*/
interface AccountsClickListener<T> {
fun onItemClick(view: View, model: T)
fun onCopyClicked(model: T)
}

View File

@@ -0,0 +1,13 @@
package com.yogeshpaliyal.keypasscompose.listener
import android.view.View
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 09:00
*/
interface UniversalClickListener<T> {
fun onItemClick(view: View, model: T)
}

View File

@@ -0,0 +1,65 @@
package com.yogeshpaliyal.keypasscompose.ui
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.yogeshpaliyal.keypasscompose.BuildConfig
import com.yogeshpaliyal.keypasscompose.databinding.ActivityCrashBinding
import dagger.hilt.android.AndroidEntryPoint
import java.lang.StringBuilder
@AndroidEntryPoint
class CrashActivity : AppCompatActivity() {
private lateinit var binding: ActivityCrashBinding
companion object {
private const val ARG_DATA = "arg_data"
fun getIntent(context: Context, data: String?): Intent {
return Intent(context, CrashActivity::class.java).also {
it.putExtra(ARG_DATA, data)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCrashBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.txtCrash.text = intent.extras?.getString(ARG_DATA)
binding.btnSendFeedback.setOnClickListener {
val deviceInfo = StringBuilder()
try {
deviceInfo.append("\n")
deviceInfo.append("App Version: " + BuildConfig.VERSION_NAME)
deviceInfo.append("\n")
deviceInfo.append("Brand Name: " + Build.BRAND)
deviceInfo.append("\n")
deviceInfo.append("Manufacturer Name: " + Build.MANUFACTURER)
deviceInfo.append("\n")
deviceInfo.append("Device Name: " + Build.MODEL)
deviceInfo.append("\n")
deviceInfo.append("Device API Version: " + Build.VERSION.SDK_INT)
deviceInfo.append("\n")
} catch (e: Exception) {
e.printStackTrace()
}
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("yogeshpaliyal.foss@gmail.com"))
intent.putExtra(Intent.EXTRA_SUBJECT, "Crash Report in KeyPass")
intent.putExtra(Intent.EXTRA_TEXT, binding.txtCrash.text.toString() + "$deviceInfo")
startActivity(Intent.createChooser(intent, ""))
}
}
}

View File

@@ -0,0 +1,139 @@
package com.yogeshpaliyal.keypasscompose.ui.addTOTP
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.zxing.integration.android.IntentIntegrator
import com.yogeshpaliyal.common.utils.TOTPHelper
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.ActivityAddTotpactivityBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AddTOTPActivity : AppCompatActivity() {
companion object {
private const val ARG_ACCOUNT_ID = "account_id"
@JvmStatic
fun start(context: Context?, accountId: String? = null) {
val starter = Intent(context, AddTOTPActivity::class.java)
starter.putExtra(ARG_ACCOUNT_ID, accountId)
context?.startActivity(starter)
}
}
private lateinit var binding: ActivityAddTotpactivityBinding
private val mViewModel by viewModels<AddTOTPViewModel>()
private val accountId by lazy {
intent.extras?.getString(ARG_ACCOUNT_ID)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddTotpactivityBinding.inflate(layoutInflater)
binding.mViewModel = mViewModel
binding.lifecycleOwner = this
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
binding.tilSecretKey.isVisible = accountId == null
mViewModel.loadOldAccount(accountId)
binding.toolbar.setNavigationOnClickListener {
onBackPressed()
}
binding.tilSecretKey.setEndIconOnClickListener {
// ScannerActivity.start(this)
IntentIntegrator(this).setPrompt("").initiateScan()
}
mViewModel.error.observe(
this,
Observer {
it?.getContentIfNotHandled()?.let {
Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT).show()
}
}
)
mViewModel.goBack.observe(
this,
Observer {
it.getContentIfNotHandled()?.let {
onBackPressed()
}
}
)
binding.btnSave.setOnClickListener {
mViewModel.saveAccount(accountId)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (accountId != null)
menuInflater.inflate(R.menu.menu_delete, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_delete) {
deleteAccount()
}
return super.onOptionsItemSelected(item)
}
private fun deleteAccount() {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.delete_account_title))
.setMessage(getString(R.string.delete_account_msg))
.setPositiveButton(
getString(R.string.delete)
) { dialog, which ->
dialog?.dismiss()
if (accountId != null) {
mViewModel.deleteAccount(accountId!!) {
onBackPressed()
}
}
}
.setNegativeButton(getString(R.string.cancel)) { dialog, which ->
dialog.dismiss()
}.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents != null) {
try {
val totp = TOTPHelper(result.contents)
mViewModel.setSecretKey(totp.secret)
mViewModel.setAccountName(totp.label)
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}

View File

@@ -0,0 +1,85 @@
package com.yogeshpaliyal.keypasscompose.ui.addTOTP
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.AppDatabase
import com.yogeshpaliyal.common.constants.AccountType
import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.common.utils.Event
import com.yogeshpaliyal.keypasscompose.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class AddTOTPViewModel @Inject constructor(private val appDatabase: AppDatabase) :
ViewModel() {
private val _goBack = MutableLiveData<Event<Unit>>()
val goBack: LiveData<Event<Unit>> = _goBack
private val _error = MutableLiveData<Event<Int>>()
val error: LiveData<Event<Int>> = _error
val secretKey = MutableLiveData<String>("")
val accountName = MutableLiveData<String>("")
fun loadOldAccount(accountId: String?) {
accountId ?: return
viewModelScope.launch(Dispatchers.IO) {
appDatabase.getDao().getAccount(accountId)?.let { accountModel ->
accountName.postValue(accountModel.title ?: "")
}
}
}
fun saveAccount(accountId: String?) {
viewModelScope.launch {
val secretKey = secretKey.value
val accountName = accountName.value
if (accountId == null) {
if (secretKey.isNullOrEmpty()) {
_error.postValue(Event(R.string.alert_black_secret_key))
return@launch
}
}
if (accountName.isNullOrEmpty()) {
_error.postValue(Event(R.string.alert_black_account_name))
return@launch
}
val accountModel = if (accountId == null) {
AccountModel(password = secretKey, title = accountName, type = AccountType.TOTP)
} else {
appDatabase.getDao().getAccount(accountId)?.also {
it.title = accountName
}
}
accountModel?.let { appDatabase.getDao().insertOrUpdateAccount(it) }
_goBack.postValue(Event(Unit))
}
}
fun setSecretKey(secretKey: String) {
this.secretKey.value = secretKey
}
fun setAccountName(accountName: String) {
this.accountName.value = accountName
}
fun deleteAccount(accountId: String, onDeleted: () -> Unit) {
viewModelScope.launch {
appDatabase.getDao().deleteAccount(accountId)
onDeleted()
}
}
}

View File

@@ -0,0 +1,55 @@
package com.yogeshpaliyal.keypasscompose.ui.addTOTP
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.yogeshpaliyal.common.constants.RequestCodes
import com.yogeshpaliyal.keypasscompose.databinding.ActivityScannerBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ScannerActivity : AppCompatActivity() {
private lateinit var binding: ActivityScannerBinding
private val REQUEST_CAM_PERMISSION = 432
companion object {
@JvmStatic
fun start(activity: Activity) {
val starter = Intent(activity, ScannerActivity::class.java)
activity.startActivityForResult(starter, RequestCodes.SCANNER)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityScannerBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
binding.toolbar.setNavigationOnClickListener {
onBackPressed()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CAM_PERMISSION) {
if (isAllRequestGranted(grantResults)) {
// codeScanner?.startPreview()
}
}
}
private fun isAllRequestGranted(grantResults: IntArray) =
grantResults.all { it == PackageManager.PERMISSION_GRANTED }
}

View File

@@ -0,0 +1,133 @@
package com.yogeshpaliyal.keypasscompose.ui.auth
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.ActivityAuthenticationBinding
import com.yogeshpaliyal.keypasscompose.ui.nav.DashboardActivity
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.Executor
private const val AUTHENTICATION_RESULT = 707
@AndroidEntryPoint
class AuthenticationActivity : AppCompatActivity() {
private lateinit var binding: ActivityAuthenticationBinding
private lateinit var executor: Executor
private lateinit var biometricPrompt: BiometricPrompt
private lateinit var promptInfo: BiometricPrompt.PromptInfo
private val biometricManager by lazy {
BiometricManager.from(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAuthenticationBinding.inflate(layoutInflater)
setContentView(binding.root)
executor = ContextCompat.getMainExecutor(this)
biometricPrompt = BiometricPrompt(
this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
Toast.makeText(
applicationContext,
"Authentication error: $errString", Toast.LENGTH_SHORT
)
.show()
// finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
onAuthenticated()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Toast.makeText(
applicationContext, getString(R.string.authentication_failed),
Toast.LENGTH_SHORT
)
.show()
}
}
)
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.app_name))
.setSubtitle(getString(R.string.login_to_enter_keypass))
.setAllowedAuthenticators(DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG)
.build()
// Prompt appears when user clicks "Log in".
// Consider integrating with the keystore to unlock cryptographic operations,
// if needed by your app.
biometricPrompt.authenticate(promptInfo)
binding.btnRetry.setOnClickListener {
val allowedAuths = DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG
val canAuthentication =
biometricManager.canAuthenticate(allowedAuths)
when (canAuthentication) {
BiometricManager.BIOMETRIC_SUCCESS -> {
Log.d("MY_APP_TAG", "App can authenticate using biometrics.")
biometricPrompt.authenticate(promptInfo)
}
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
Log.e(
"MY_APP_TAG",
"$canAuthentication Biometric features are currently unavailable."
)
// Prompts the user to create credentials that your app accepts.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
)
}
startActivityForResult(enrollIntent, AUTHENTICATION_RESULT)
} else {
Toast.makeText(
this,
"Please set password for your device first from phone settings",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
private fun onAuthenticated() {
// binding.passCodeView.isVisible = false
val dashboardIntent = Intent(this, DashboardActivity::class.java)
startActivity(dashboardIntent)
finish()
}
}

View File

@@ -0,0 +1,332 @@
package com.yogeshpaliyal.keypasscompose.ui.backup
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.common.utils.BACKUP_KEY_LENGTH
import com.yogeshpaliyal.common.utils.backupAccounts
import com.yogeshpaliyal.common.utils.canUserAccessBackupDirectory
import com.yogeshpaliyal.common.utils.clearBackupKey
import com.yogeshpaliyal.common.utils.formatCalendar
import com.yogeshpaliyal.common.utils.getBackupDirectory
import com.yogeshpaliyal.common.utils.getBackupTime
import com.yogeshpaliyal.common.utils.getOrCreateBackupKey
import com.yogeshpaliyal.common.utils.isAutoBackupEnabled
import com.yogeshpaliyal.common.utils.isKeyPresent
import com.yogeshpaliyal.common.utils.overrideAutoBackup
import com.yogeshpaliyal.common.utils.saveKeyphrase
import com.yogeshpaliyal.common.utils.setAutoBackupEnabled
import com.yogeshpaliyal.common.utils.setBackupDirectory
import com.yogeshpaliyal.common.utils.setBackupTime
import com.yogeshpaliyal.common.utils.setOverrideAutoBackup
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.BackupActivityBinding
import com.yogeshpaliyal.keypasscompose.databinding.LayoutBackupKeypharseBinding
import com.yogeshpaliyal.keypasscompose.databinding.LayoutCustomKeypharseBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URLDecoder
import javax.inject.Inject
@AndroidEntryPoint
class BackupActivity : AppCompatActivity() {
companion object {
@JvmStatic
fun start(context: Context?) {
val starter = Intent(context, BackupActivity::class.java)
context?.startActivity(starter)
}
}
private lateinit var binding: BackupActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = BackupActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
binding.toolbar.setNavigationOnClickListener {
onBackPressed()
}
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var appDb: com.yogeshpaliyal.common.AppDatabase
private val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.backup_preferences, rootKey)
updateItems()
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
getString(R.string.settings_start_backup) -> {
startBackup()
}
getString(R.string.settings_create_backup) -> {
lifecycleScope.launch {
if (context.canUserAccessBackupDirectory()) {
val selectedDirectory = Uri.parse(context.getBackupDirectory())
passwordSelection(selectedDirectory)
}
}
}
getString(R.string.settings_backup_folder) -> {
changeBackupFolder()
}
getString(R.string.settings_verify_key_phrase) -> {
verifyKeyPhrase()
}
getString(R.string.settings_stop_backup) -> {
lifecycleScope.launch {
stopBackup()
}
}
getString(R.string.settings_auto_backup) -> {
lifecycleScope.launch {
context.setAutoBackupEnabled(context.isAutoBackupEnabled().not())
updateItems()
}
}
getString(R.string.settings_override_auto_backup) -> {
lifecycleScope.launch {
context.setOverrideAutoBackup(context.overrideAutoBackup().not())
updateItems()
}
}
}
return super.onPreferenceTreeClick(preference)
}
private suspend fun passwordSelection(selectedDirectory: Uri) {
val isKeyPresent = context?.isKeyPresent() ?: return
if (isKeyPresent) {
backup(selectedDirectory)
return
}
val builder = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.alert)
.setMessage(getString(R.string.custom_generated_keyphrase_info))
.setPositiveButton(
getString(R.string.custom_keyphrase)
) { dialog, which ->
dialog?.dismiss()
setCustomKeyphrase(selectedDirectory)
}
.setNegativeButton(R.string.generate_keyphrase) { dialog, which ->
dialog?.dismiss()
backup(selectedDirectory)
}
builder.show()
}
private fun setCustomKeyphrase(selectedDirectory: Uri) {
val binding = LayoutCustomKeypharseBinding.inflate(layoutInflater)
val dialog = MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
getString(R.string.yes)
) { dialog, which ->
dialog?.dismiss()
}.create()
dialog.setOnShowListener {
val positiveBtn = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
positiveBtn.setOnClickListener {
val keyphrase = binding.etKeyPhrase.text.toString().trim()
if (keyphrase.isEmpty()) {
Toast.makeText(context, R.string.alert_blank_keyphrase, Toast.LENGTH_SHORT)
.show()
return@setOnClickListener
}
if (keyphrase.length != BACKUP_KEY_LENGTH) {
Toast.makeText(
context,
R.string.alert_invalid_keyphrase,
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
lifecycleScope.launch {
context?.saveKeyphrase(keyphrase)
backup(selectedDirectory)
}
dialog.dismiss()
}
}
dialog.show()
}
fun backup(selectedDirectory: Uri) {
lifecycleScope.launch {
context.backupAccounts(appDb, selectedDirectory)?.let { keyPair ->
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = context?.getOrCreateBackupKey()?.second ?: ""
binding.txtCode.setOnClickListener {
val clipboard =
ContextCompat.getSystemService(
requireContext(),
ClipboardManager::class.java
)
val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
clipboard?.setPrimaryClip(clip)
Toast.makeText(
context,
getString(R.string.copied_to_clipboard),
Toast.LENGTH_SHORT
).show()
}
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
getString(R.string.yes)
) { dialog, which ->
updateItems()
dialog?.dismiss()
}.show()
} else {
updateItems()
Toast.makeText(
context,
getString(R.string.backup_completed),
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun updateItems() {
lifecycleScope.launch(Dispatchers.IO) {
val isBackupEnabled =
context.canUserAccessBackupDirectory() && (context?.isKeyPresent() ?: false)
val isAutoBackupEnabled = context.isAutoBackupEnabled()
val overrideAutoBackup = context.overrideAutoBackup()
val lastBackupTime = context.getBackupTime()
val backupDirectory = context.getBackupDirectory()
withContext(Dispatchers.Main) {
findPreference<Preference>(getString(R.string.settings_start_backup))?.isVisible =
isBackupEnabled.not()
findPreference<Preference>(getString(R.string.settings_stop_backup))?.isVisible =
isBackupEnabled
findPreference<Preference>(getString(R.string.settings_auto_backup))?.isVisible =
isBackupEnabled
findPreference<Preference>(getString(R.string.settings_auto_backup))?.summary =
if (isAutoBackupEnabled) getString(R.string.enabled) else getString(R.string.disabled)
findPreference<PreferenceCategory>(getString(R.string.settings_cat_auto_backup))?.isVisible =
isBackupEnabled && isAutoBackupEnabled
findPreference<Preference>(getString(R.string.settings_override_auto_backup))?.summary =
if (overrideAutoBackup) getString(R.string.enabled) else getString(R.string.disabled)
findPreference<Preference>(getString(R.string.settings_create_backup))?.isVisible =
isBackupEnabled
findPreference<Preference>(getString(R.string.settings_create_backup))?.summary =
getString(
R.string.last_backup_date,
lastBackupTime.formatCalendar("dd MMM yyyy hh:mm aa")
)
findPreference<Preference>(getString(R.string.settings_backup_folder))?.isVisible =
isBackupEnabled
val directory = URLDecoder.decode(backupDirectory, "utf-8").split("/")
val folderName = directory.get(directory.lastIndex)
findPreference<Preference>(getString(R.string.settings_backup_folder))?.summary =
folderName
findPreference<Preference>(getString(R.string.settings_verify_key_phrase))?.isVisible =
false
findPreference<Preference>(getString(R.string.settings_backup))?.isVisible =
isBackupEnabled
}
}
}
private fun startBackup() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
try {
startActivityForResult(intent, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val contentResolver = context?.contentResolver
val selectedDirectory = data?.data
if (contentResolver != null && selectedDirectory != null) {
contentResolver.takePersistableUriPermission(
selectedDirectory,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
lifecycleScope.launch {
context.setBackupDirectory(selectedDirectory.toString())
passwordSelection(selectedDirectory)
}
}
}
}
private fun changeBackupFolder() {
startBackup()
}
private fun verifyKeyPhrase() {
Toast.makeText(context, getString(R.string.coming_soon), Toast.LENGTH_SHORT).show()
}
private suspend fun stopBackup() {
context.clearBackupKey()
context.setBackupDirectory("")
context.setBackupTime(-1)
context.setOverrideAutoBackup(false)
context.setAutoBackupEnabled(false)
updateItems()
}
}
}

View File

@@ -0,0 +1,132 @@
package com.yogeshpaliyal.keypasscompose.ui.detail
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.zxing.integration.android.IntentIntegrator
import com.yogeshpaliyal.common.utils.PasswordGenerator
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.FragmentDetailBinding
import dagger.hilt.android.AndroidEntryPoint
/*
* @author Yogesh Paliyal
* yogeshpaliyal.foss@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 10:38
*/
@AndroidEntryPoint
class DetailActivity : AppCompatActivity() {
lateinit var binding: FragmentDetailBinding
companion object {
private const val ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"
@JvmStatic
fun start(context: Context?, accountId: Long? = null) {
val starter = Intent(context, DetailActivity::class.java)
.putExtra(ARG_ACCOUNT_ID, accountId)
context?.startActivity(starter)
}
}
private val mViewModel by viewModels<DetailViewModel>()
private val accountId by lazy {
intent?.extras?.getLong(ARG_ACCOUNT_ID) ?: -1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.lifecycleOwner = this
mViewModel.loadAccount(accountId)
mViewModel.accountModel.observe(
this,
Observer {
binding.accountData = it
}
)
if (accountId > 0) {
binding.bottomAppBar.replaceMenu(R.menu.bottom_app_bar_detail)
binding.tilPassword.startIconDrawable = null
} else {
binding.tilPassword.setStartIconDrawable(R.drawable.ic_round_refresh_24)
binding.tilPassword.setStartIconOnClickListener {
binding.etPassword.setText(PasswordGenerator().generatePassword())
}
}
binding.bottomAppBar.setNavigationOnClickListener {
onBackPressed()
}
binding.bottomAppBar.setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_delete) {
deleteAccount()
return@setOnMenuItemClickListener true
}
return@setOnMenuItemClickListener false
}
binding.btnSave.setOnClickListener {
mViewModel.insertOrUpdate {
onBackPressed()
}
}
binding.btnScan.setOnClickListener {
IntentIntegrator(this).setPrompt("").initiateScan()
}
}
private fun deleteAccount() {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.delete_account_title))
.setMessage(getString(R.string.delete_account_msg))
.setPositiveButton(
getString(R.string.delete)
) { dialog, which ->
dialog?.dismiss()
if (accountId > 0L) {
mViewModel.deleteAccount {
onBackPressed()
}
}
}
.setNegativeButton(getString(R.string.cancel)) { dialog, which ->
dialog.dismiss()
}.show()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.bottom_app_bar_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents != null) {
binding.etPassword.setText(result.contents)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}

View File

@@ -0,0 +1,68 @@
package com.yogeshpaliyal.keypasscompose.ui.detail
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.common.worker.executeAutoBackup
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 11:52
*/
@HiltViewModel
class DetailViewModel @Inject constructor(
val app: Application,
val appDb: com.yogeshpaliyal.common.AppDatabase
) : AndroidViewModel(app) {
private val _accountModel by lazy { MutableLiveData<AccountModel>() }
val accountModel: LiveData<AccountModel> = _accountModel
fun loadAccount(accountId: Long?) {
viewModelScope.launch(Dispatchers.IO) {
_accountModel.postValue(
appDb.getDao().getAccount(accountId) ?: AccountModel()
)
}
}
fun deleteAccount(onExecCompleted: () -> Unit) {
viewModelScope.launch {
accountModel.value?.let {
withContext(Dispatchers.IO) {
appDb.getDao().deleteAccount(it)
}
autoBackup()
onExecCompleted()
}
}
}
fun insertOrUpdate(onExecCompleted: () -> Unit) {
viewModelScope.launch {
accountModel.value?.let {
withContext(Dispatchers.IO) {
appDb.getDao().insertOrUpdateAccount(it)
autoBackup()
}
}
onExecCompleted()
}
}
private fun autoBackup() {
viewModelScope.launch {
app.executeAutoBackup()
}
}
}

View File

@@ -0,0 +1,47 @@
package com.yogeshpaliyal.keypasscompose.ui.generate
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.yogeshpaliyal.common.utils.PasswordGenerator
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.ActivityGeneratePasswordBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class GeneratePasswordActivity : AppCompatActivity() {
private lateinit var binding: ActivityGeneratePasswordBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityGeneratePasswordBinding.inflate(layoutInflater)
setContentView(binding.root)
generatePassword()
binding.btnRefresh.setOnClickListener {
generatePassword()
}
binding.tilPassword.setEndIconOnClickListener {
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("random_password", binding.etPassword.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
}
}
private fun generatePassword() {
val password = PasswordGenerator(
length = binding.sliderPasswordLength.value.toInt(),
includeUpperCaseLetters = binding.cbCapAlphabets.isChecked,
includeLowerCaseLetters = binding.cbLowerAlphabets.isChecked,
includeSymbols = binding.cbSymbols.isChecked,
includeNumbers = binding.cbNumbers.isChecked
).generatePassword()
binding.etPassword.setText(password)
binding.etPassword.setSelection(password.length)
}
}

View File

@@ -0,0 +1,57 @@
package com.yogeshpaliyal.keypasscompose.ui.home
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.data.AccountModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 30-01-2021 23:02
*/
@HiltViewModel
class DashboardViewModel @Inject constructor(application: Application, val appDb: com.yogeshpaliyal.common.AppDatabase) :
AndroidViewModel(application) {
val keyword by lazy {
MutableLiveData<String>("")
}
val tag by lazy {
MutableLiveData<String>()
}
private val appDao = appDb.getDao()
val mediator = MediatorLiveData<LiveData<List<AccountModel>>>()
init {
mediator.addSource(keyword) {
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
}
mediator.addSource(tag) {
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
}
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
reloadData()
}
private fun reloadData() {
viewModelScope.launch(Dispatchers.IO) {
while (true) {
delay(1000)
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
}
}
}
}

View File

@@ -0,0 +1,121 @@
package com.yogeshpaliyal.keypasscompose.ui.home
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.yogeshpaliyal.common.constants.AccountType
import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.data.MyAccountModel
import com.yogeshpaliyal.keypasscompose.databinding.FragmentHomeBinding
import com.yogeshpaliyal.keypasscompose.listener.AccountsClickListener
import com.yogeshpaliyal.keypasscompose.ui.addTOTP.AddTOTPActivity
import com.yogeshpaliyal.keypasscompose.ui.detail.DetailActivity
import com.yogeshpaliyal.universalAdapter.adapter.UniversalAdapterViewType
import com.yogeshpaliyal.universalAdapter.adapter.UniversalRecyclerAdapter
import com.yogeshpaliyal.universalAdapter.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 09:25
*/
@AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private val mViewModel by lazy {
activityViewModels<DashboardViewModel>().value
}
private val mAdapter by lazy {
UniversalRecyclerAdapter.Builder<MyAccountModel>(
this,
content = UniversalAdapterViewType.Content(
R.layout.item_accounts,
listener = mListener
),
noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts)
).build()
}
val mListener = object : AccountsClickListener<AccountModel> {
override fun onItemClick(view: View, model: AccountModel) {
if (model.type == AccountType.TOTP) {
AddTOTPActivity.start(context, model.uniqueId)
} else {
DetailActivity.start(context, model.id)
}
}
private fun getPassword(model: AccountModel): String {
if (model.type == AccountType.TOTP) {
return model.getOtp()
}
return model.password.orEmpty()
}
override fun onCopyClicked(model: AccountModel) {
val clipboard =
ContextCompat.getSystemService(
requireContext(),
ClipboardManager::class.java
)
val clip = ClipData.newPlainText("KeyPass", getPassword(model))
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
return binding.root
}
private val observer = Observer<List<AccountModel>> {
val newList = it.map { accountModel ->
MyAccountModel().also {
it.map(accountModel)
}
}
mAdapter.updateData(Resource.success(ArrayList(newList)))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.adapter = mAdapter.getAdapter()
mViewModel.mediator.observe(
viewLifecycleOwner,
Observer {
it.removeObserver(observer)
it.observe(viewLifecycleOwner, observer)
}
)
/* lifecycleScope.launch() {
mViewModel.result
mViewModel.loadData(args.tag).collect {
withContext(Dispatchers.Main) {
mAdapter.updateData(Resource.success(ArrayList(it)))
}
}
}*/
}
}

View File

@@ -0,0 +1,173 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.observe
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.google.android.material.bottomsheet.BottomSheetBehavior.from
import com.google.android.material.shape.MaterialShapeDrawable
import com.yogeshpaliyal.common.utils.themeColor
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.FragmentBottomNavDrawerBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlin.LazyThreadSafetyMode.NONE
/**
* A [Fragment] which acts as a bottom navigation drawer.
*/
@AndroidEntryPoint
class BottomNavDrawerFragment :
Fragment(),
NavigationAdapter.NavigationAdapterListener {
private lateinit var binding: FragmentBottomNavDrawerBinding
private val behavior: BottomSheetBehavior<FrameLayout> by lazy(NONE) {
from(binding.foregroundContainer)
}
private val mViewModel by viewModels<BottomNavViewModel>()
private val bottomSheetCallback = BottomNavigationDrawerCallback()
private val navigationListeners: MutableList<NavigationAdapter.NavigationAdapterListener> =
mutableListOf()
private val foregroundShapeDrawable: MaterialShapeDrawable by lazy(NONE) {
val foregroundContext = binding.foregroundContainer.context
MaterialShapeDrawable(
foregroundContext,
null,
R.attr.bottomSheetStyle,
0
).apply {
fillColor = ColorStateList.valueOf(
foregroundContext.themeColor(R.attr.colorSurface)
)
elevation = resources.getDimension(R.dimen.plane_16)
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_NEVER
initializeElevationOverlay(requireContext())
}
}
private val closeDrawerOnBackPressed = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
close()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(this, closeDrawerOnBackPressed)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentBottomNavDrawerBinding.inflate(inflater, container, false)
binding.foregroundContainer.setOnApplyWindowInsetsListener { view, windowInsets ->
// Record the window's top inset so it can be applied when the bottom sheet is slide up
// to meet the top edge of the screen.
view.setTag(
R.id.tag_system_window_inset_top,
windowInsets.systemWindowInsetTop
)
windowInsets
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.run {
// backgroundContainer.background = backgroundShapeDrawable
foregroundContainer.background = foregroundShapeDrawable
scrimView.setOnClickListener { close() }
bottomSheetCallback.apply {
// Scrim view transforms
addOnSlideAction(AlphaSlideAction(scrimView))
addOnStateChangedAction(VisibilityStateAction(scrimView))
// Recycler transforms
addOnStateChangedAction(ScrollToTopStateAction(navRecyclerView))
// Close the sandwiching account picker if open
addOnStateChangedAction(object : OnStateChangedAction {
override fun onStateChanged(sheet: View, newState: Int) {
}
})
// If the drawer is open, pressing the system back button should close the drawer.
addOnStateChangedAction(object : OnStateChangedAction {
override fun onStateChanged(sheet: View, newState: Int) {
closeDrawerOnBackPressed.isEnabled = newState != STATE_HIDDEN
}
})
}
behavior.addBottomSheetCallback(bottomSheetCallback)
behavior.state = STATE_HIDDEN
val adapter = NavigationAdapter(this@BottomNavDrawerFragment)
navRecyclerView.adapter = adapter
mViewModel.navigationList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
mViewModel.setNavigationMenuItemChecked(0)
}
}
fun open() {
behavior.state = STATE_HALF_EXPANDED
}
fun close() {
behavior.state = STATE_HIDDEN
}
fun addOnSlideAction(action: OnSlideAction) {
bottomSheetCallback.addOnSlideAction(action)
}
fun addOnStateChangedAction(action: OnStateChangedAction) {
bottomSheetCallback.addOnStateChangedAction(action)
}
fun addNavigationListener(listener: NavigationAdapter.NavigationAdapterListener) {
navigationListeners.add(listener)
}
override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
// mViewModel.setNavigationMenuItemChecked(item.id)
close()
navigationListeners.forEach { it.onNavMenuItemClicked(item) }
}
override fun onNavEmailFolderClicked(folder: NavigationModelItem.NavEmailFolder) {
navigationListeners.forEach { it.onNavEmailFolderClicked(folder) }
}
fun toggle() {
when {
behavior.state == STATE_HIDDEN -> open()
behavior.state == STATE_HIDDEN ||
behavior.state == STATE_HALF_EXPANDED ||
behavior.state == STATE_EXPANDED
|| behavior.state == STATE_COLLAPSED -> close()
}
}
}

View File

@@ -0,0 +1,67 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 14:11
*/
@HiltViewModel
class BottomNavViewModel @Inject constructor (application: Application, val appDb: com.yogeshpaliyal.common.AppDatabase) : AndroidViewModel(application) {
private val _navigationList: MutableLiveData<List<NavigationModelItem>> = MutableLiveData()
private val tagsDb = appDb.getDao().getTags()
private var tagsList: List<String> ? = null
val navigationList: LiveData<List<NavigationModelItem>>
get() = _navigationList
init {
postListUpdate()
viewModelScope.launch {
tagsDb.collect {
tagsList = it
postListUpdate()
}
}
}
/**
* Set the currently selected menu item.
*
* @return true if the currently selected item has changed.
*/
fun setNavigationMenuItemChecked(id: Int): Boolean {
var updated = false
NavigationModel.navigationMenuItems.forEachIndexed { index, item ->
val shouldCheck = item.id == id
if (item.checked != shouldCheck) {
NavigationModel.navigationMenuItems[index] = item.copy(checked = shouldCheck)
updated = true
}
}
if (updated) postListUpdate()
return updated
}
private fun postListUpdate() {
val newList = if (tagsList.isNullOrEmpty().not()) {
NavigationModel.navigationMenuItems + NavigationModelItem.NavDivider("Tags") + (tagsList?.filter { it != null }?.map { NavigationModelItem.NavEmailFolder(it) } ?: listOf())
} else {
NavigationModel.navigationMenuItems
}
_navigationList.value = newList
}
}

View File

@@ -0,0 +1,92 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.annotation.SuppressLint
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.yogeshpaliyal.common.utils.normalize
import kotlin.math.max
/**
* A [BottomSheetBehavior.BottomSheetCallback] which helps break apart clients who would like to
* react to changed in either the bottom sheet's slide offset or state. Clients can dynamically
* add or remove [OnSlideAction]s or [OnStateChangedAction]s which will be run when the
* sheet's slideOffset or state are changed.
*
* This callback's behavior differs slightly in that the slideOffset passed to [OnSlideAction]s
* in [onSlide] is corrected to guarantee that the offset 0.0 <i>always</i> be exactly at the
* [BottomSheetBehavior.STATE_HALF_EXPANDED] state.
*/
class BottomNavigationDrawerCallback : BottomSheetBehavior.BottomSheetCallback() {
private val onSlideActions: MutableList<OnSlideAction> = mutableListOf()
private val onStateChangedActions: MutableList<OnStateChangedAction> = mutableListOf()
private var lastSlideOffset = -1.0F
private var halfExpandedSlideOffset = Float.MAX_VALUE
override fun onSlide(sheet: View, slideOffset: Float) {
if (halfExpandedSlideOffset == Float.MAX_VALUE)
calculateInitialHalfExpandedSlideOffset(sheet)
lastSlideOffset = slideOffset
// Correct for the fact that the slideOffset is not zero when half expanded
val trueOffset = if (slideOffset <= halfExpandedSlideOffset) {
slideOffset.normalize(-1F, halfExpandedSlideOffset, -1F, 0F)
} else {
slideOffset.normalize(halfExpandedSlideOffset, 1F, 0F, 1F)
}
onSlideActions.forEach { it.onSlide(sheet, trueOffset) }
}
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) {
halfExpandedSlideOffset = lastSlideOffset
onSlide(sheet, lastSlideOffset)
}
onStateChangedActions.forEach { it.onStateChanged(sheet, newState) }
}
/**
* Calculate the onSlideOffset which will be given when the bottom sheet is in the
* [BottomSheetBehavior.STATE_HALF_EXPANDED] state.
*
* Recording the correct slide offset for the half expanded state happens in [onStateChanged].
* Since the first time the sheet is opened, we haven't yet received a call to [onStateChanged],
* this method is used to calculate the initial value manually so we can smoothly normalize
* slideOffset values received between -1 and 1.
*
* See:
* [BottomSheetBehavior.calculateCollapsedOffset]
* [BottomSheetBehavior.calculateHalfExpandedOffset]
* [BottomSheetBehavior.dispatchOnSlide]
*/
@SuppressLint("PrivateResource")
private fun calculateInitialHalfExpandedSlideOffset(sheet: View) {
val parent = sheet.parent as CoordinatorLayout
val behavior = BottomSheetBehavior.from(sheet)
val halfExpandedOffset = parent.height * (1 - behavior.halfExpandedRatio)
val peekHeightMin = parent.resources.getDimensionPixelSize(
R.dimen.design_bottom_sheet_peek_height_min
)
val peek = max(peekHeightMin, parent.height - parent.width * 9 / 16)
val collapsedOffset = max(
parent.height - peek,
max(0, parent.height - sheet.height)
)
halfExpandedSlideOffset =
(collapsedOffset - halfExpandedOffset) / (parent.height - collapsedOffset)
}
fun addOnSlideAction(action: OnSlideAction): Boolean {
return onSlideActions.add(action)
}
fun addOnStateChangedAction(action: OnStateChangedAction): Boolean {
return onStateChangedActions.add(action)
}
}

View File

@@ -0,0 +1,233 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.annotation.MenuRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.transition.MaterialElevationScale
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.ActivityDashboardBinding
import com.yogeshpaliyal.keypasscompose.ui.addTOTP.AddTOTPActivity
import com.yogeshpaliyal.keypasscompose.ui.detail.DetailActivity
import com.yogeshpaliyal.keypasscompose.ui.generate.GeneratePasswordActivity
import com.yogeshpaliyal.keypasscompose.ui.home.DashboardViewModel
import com.yogeshpaliyal.keypasscompose.ui.home.HomeFragmentDirections
import com.yogeshpaliyal.keypasscompose.ui.settings.MySettingsFragmentDirections
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DashboardActivity :
AppCompatActivity(),
Toolbar.OnMenuItemClickListener,
NavController.OnDestinationChangedListener,
NavigationAdapter.NavigationAdapterListener {
lateinit var binding: ActivityDashboardBinding
private val bottomNavDrawer: BottomNavDrawerFragment by lazy(LazyThreadSafetyMode.NONE) {
supportFragmentManager.findFragmentById(R.id.bottom_nav_drawer) as BottomNavDrawerFragment
}
private val mViewModel by viewModels<DashboardViewModel>()
private val currentNavigationFragment: Fragment?
get() = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
?.childFragmentManager
?.fragments
?.first()
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
binding = ActivityDashboardBinding.inflate(layoutInflater)
setContentView(binding.root)
// setSupportActionBar(binding.bottomAppBar)
binding.lifecycleOwner = this
binding.viewModel = mViewModel
navController.addOnDestinationChangedListener(
this@DashboardActivity
)
/* val intent = Intent(this, AuthenticationActivity::class.java)
startActivity(intent)*/
/* val autoFillService = getAutoFillService()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
if (autoFillService?.isAutofillSupported == true && autoFillService.hasEnabledAutofillServices().not()) {
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
intent.data = Uri.parse("package:$packageName")
startActivityForResult(intent,777)
}
}*/
binding.btnAdd.setOnClickListener {
currentNavigationFragment?.apply {
exitTransition = MaterialElevationScale(false).apply {
duration =
resources.getInteger(R.integer.keypass_motion_duration_large).toLong()
}
reenterTransition = MaterialElevationScale(true).apply {
duration =
resources.getInteger(R.integer.keypass_motion_duration_large).toLong()
}
}
DetailActivity.start(this)
}
bottomNavDrawer.apply {
// addOnSlideAction(HalfClockwiseRotateSlideAction(binding.bottomAppBar))
// addOnSlideAction(AlphaSlideAction(binding.bottomAppBarTitle, true))
addOnStateChangedAction(ShowHideFabStateAction(binding.btnAdd))
addOnStateChangedAction(
ChangeSettingsMenuStateAction { showSettings ->
// Toggle between the current destination's BAB menu and the menu which should
// be displayed when the BottomNavigationDrawer is open.
binding.bottomAppBar.replaceMenu(
if (showSettings) {
R.menu.bottom_app_bar_settings_menu
} else {
getBottomAppBarMenuForDestination()
}
)
}
)
// addOnSandwichSlideAction(HalfCounterClockwiseRotateSlideAction(binding.bottomAppBarChevron))
addNavigationListener(this@DashboardActivity)
}
// Set up the BottomAppBar menu
binding.bottomAppBar.apply {
setNavigationOnClickListener {
bottomNavDrawer.toggle()
}
setOnMenuItemClickListener(this@DashboardActivity)
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.action_settings -> {
val settingDestination = MySettingsFragmentDirections.actionGlobalSettings()
navController.navigate(settingDestination)
bottomNavDrawer.close()
}
}
return true
}
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
binding.searchAppBar.isVisible = destination.id == R.id.homeFragment
when (destination.id) {
R.id.homeFragment -> {
binding.btnAdd.isActivated = false
setBottomAppBarForHome(getBottomAppBarMenuForDestination(destination))
}
}
}
override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
// Swap the list of emails for the given mailbox
// navigateToHome(item.titleRes, item.mailbox)
when (item.id) {
NavigationModel.GENERATE_PASSWORD -> {
val intent = Intent(this, GeneratePasswordActivity::class.java)
startActivity(intent)
}
NavigationModel.HOME -> {
val args = HomeFragmentDirections.actionGlobalHomeFragment()
navController.navigate(args)
}
NavigationModel.ADD_TOPT -> {
AddTOTPActivity.start(this)
}
}
}
override fun onNavEmailFolderClicked(folder: NavigationModelItem.NavEmailFolder) {
mViewModel.tag.postValue(folder.category)
val destination = HomeFragmentDirections.actionGlobalHomeFragmentTag()
navController.navigate(destination)
bottomNavDrawer.close()
}
private fun hideBottomAppBar() {
binding.run {
bottomAppBar.performHide()
// Get a handle on the animator that hides the bottom app bar so we can wait to hide
// the fab and bottom app bar until after it's exit animation finishes.
bottomAppBar.animate().setListener(object : AnimatorListenerAdapter() {
var isCanceled = false
override fun onAnimationEnd(animation: Animator) {
if (isCanceled) return
// Hide the BottomAppBar to avoid it showing above the keyboard
// when composing a new email.
bottomAppBar.visibility = View.GONE
btnAdd.visibility = View.INVISIBLE
}
override fun onAnimationCancel(animation: Animator) {
isCanceled = true
}
})
}
}
/**
* Helper function which returns the menu which should be displayed for the current
* destination.
*
* Used both when the destination has changed, centralizing destination-to-menu mapping, as
* well as switching between the alternate menu used when the BottomNavigationDrawer is
* open and closed.
*/
@MenuRes
private fun getBottomAppBarMenuForDestination(destination: NavDestination? = null): Int {
val dest = destination ?: navController.currentDestination
return when (dest?.id) {
R.id.homeFragment -> R.menu.bottom_app_bar_settings_menu
// R.id.emailFragment -> R.menu.bottom_app_bar_email_menu
else -> R.menu.bottom_app_bar_settings_menu
}
}
private fun setBottomAppBarForHome(@MenuRes menuRes: Int) {
binding.run {
btnAdd.setImageState(intArrayOf(-android.R.attr.state_activated), true)
bottomAppBar.visibility = View.VISIBLE
bottomAppBar.replaceMenu(menuRes)
// btnAdd.contentDescription = getString(R.string.fab_compose_email_content_description)
// bottomAppBarTitle.visibility = View.VISIBLE
bottomAppBar.performShow()
btnAdd.show()
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import com.yogeshpaliyal.keypasscompose.databinding.NavDividerItemLayoutBinding
import com.yogeshpaliyal.keypasscompose.databinding.NavEmailFolderItemLayoutBinding
import com.yogeshpaliyal.keypasscompose.databinding.NavMenuItemLayoutBinding
private const val VIEW_TYPE_NAV_MENU_ITEM = 4
private const val VIEW_TYPE_NAV_DIVIDER = 6
private const val VIEW_TYPE_NAV_EMAIL_FOLDER_ITEM = 5
class NavigationAdapter(
private val listener: NavigationAdapterListener
) : ListAdapter<NavigationModelItem, NavigationViewHolder<NavigationModelItem>>(
NavigationModelItem.NavModelItemDiff
) {
interface NavigationAdapterListener {
fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem)
fun onNavEmailFolderClicked(folder: NavigationModelItem.NavEmailFolder)
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is NavigationModelItem.NavMenuItem -> VIEW_TYPE_NAV_MENU_ITEM
is NavigationModelItem.NavDivider -> VIEW_TYPE_NAV_DIVIDER
is NavigationModelItem.NavEmailFolder -> VIEW_TYPE_NAV_EMAIL_FOLDER_ITEM
else -> throw RuntimeException("Unsupported ItemViewType for obj ${getItem(position)}")
}
}
@Suppress("unchecked_cast")
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NavigationViewHolder<NavigationModelItem> {
return when (viewType) {
VIEW_TYPE_NAV_MENU_ITEM -> NavigationViewHolder.NavMenuItemViewHolder(
NavMenuItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
listener
)
VIEW_TYPE_NAV_DIVIDER -> NavigationViewHolder.NavDividerViewHolder(
NavDividerItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
VIEW_TYPE_NAV_EMAIL_FOLDER_ITEM -> NavigationViewHolder.EmailFolderViewHolder(
NavEmailFolderItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
listener
)
else -> throw RuntimeException("Unsupported view holder type")
} as NavigationViewHolder<NavigationModelItem>
}
override fun onBindViewHolder(
holder: NavigationViewHolder<NavigationModelItem>,
position: Int
) {
holder.bind(getItem(position))
}
}

View File

@@ -0,0 +1,34 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import com.yogeshpaliyal.keypasscompose.R
/**
* A class which maintains and generates a navigation list to be displayed by [NavigationAdapter].
*/
object NavigationModel {
const val HOME = 0
const val GENERATE_PASSWORD = 1
const val ADD_TOPT = 2
var navigationMenuItems = mutableListOf(
NavigationModelItem.NavMenuItem(
id = HOME,
icon = R.drawable.ic_twotone_home_24,
titleRes = R.string.home,
checked = false,
),
NavigationModelItem.NavMenuItem(
id = GENERATE_PASSWORD,
icon = R.drawable.ic_twotone_vpn_key_24,
titleRes = R.string.generate_password,
checked = false,
),
NavigationModelItem.NavMenuItem(
id = ADD_TOPT,
icon = R.drawable.ic_twotone_totp,
titleRes = R.string.add_totp,
checked = false,
)
)
}

View File

@@ -0,0 +1,63 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.recyclerview.widget.DiffUtil
import com.yogeshpaliyal.keypasscompose.utils.StringDiffUtil
/**
* A sealed class which encapsulates all objects [NavigationAdapter] is able to display.
*/
sealed class NavigationModelItem {
/**
* A class which represents a checkable, navigation destination such as 'Inbox' or 'Sent'.
*/
data class NavMenuItem(
val id: Int,
@DrawableRes val icon: Int,
@StringRes val titleRes: Int,
var checked: Boolean
) : NavigationModelItem()
/**
* A class which is used to show a section divider (a subtitle and underline) between
* sections of different NavigationModelItem types.
*/
data class NavDivider(val title: String) : NavigationModelItem()
/**
* A class which is used to show an [EmailFolder] in the [NavigationAdapter].
*/
data class NavEmailFolder(val category: String) : NavigationModelItem()
object NavModelItemDiff : DiffUtil.ItemCallback<NavigationModelItem>() {
override fun areItemsTheSame(
oldItem: NavigationModelItem,
newItem: NavigationModelItem
): Boolean {
return when {
oldItem is NavMenuItem && newItem is NavMenuItem ->
oldItem.id == newItem.id
oldItem is NavEmailFolder && newItem is NavEmailFolder ->
StringDiffUtil.areItemsTheSame(oldItem.category, newItem.category)
else -> oldItem == newItem
}
}
override fun areContentsTheSame(
oldItem: NavigationModelItem,
newItem: NavigationModelItem
): Boolean {
return when {
oldItem is NavMenuItem && newItem is NavMenuItem ->
oldItem.icon == newItem.icon &&
oldItem.titleRes == newItem.titleRes &&
oldItem.checked == newItem.checked
oldItem is NavEmailFolder && newItem is NavEmailFolder ->
StringDiffUtil.areContentsTheSame(oldItem.category, newItem.category)
else -> false
}
}
}
}

View File

@@ -0,0 +1,51 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.yogeshpaliyal.keypasscompose.databinding.NavDividerItemLayoutBinding
import com.yogeshpaliyal.keypasscompose.databinding.NavEmailFolderItemLayoutBinding
import com.yogeshpaliyal.keypasscompose.databinding.NavMenuItemLayoutBinding
sealed class NavigationViewHolder<T : NavigationModelItem>(
view: View
) : RecyclerView.ViewHolder(view) {
abstract fun bind(navItem: T)
class NavMenuItemViewHolder(
private val binding: NavMenuItemLayoutBinding,
private val listener: NavigationAdapter.NavigationAdapterListener
) : NavigationViewHolder<NavigationModelItem.NavMenuItem>(binding.root) {
override fun bind(navItem: NavigationModelItem.NavMenuItem) {
binding.run {
navMenuItem = navItem
navListener = listener
executePendingBindings()
}
}
}
class NavDividerViewHolder(
private val binding: NavDividerItemLayoutBinding
) : NavigationViewHolder<NavigationModelItem.NavDivider>(binding.root) {
override fun bind(navItem: NavigationModelItem.NavDivider) {
binding.navDivider = navItem
binding.executePendingBindings()
}
}
class EmailFolderViewHolder(
private val binding: NavEmailFolderItemLayoutBinding,
private val listener: NavigationAdapter.NavigationAdapterListener
) : NavigationViewHolder<NavigationModelItem.NavEmailFolder>(binding.root) {
override fun bind(navItem: NavigationModelItem.NavEmailFolder) {
binding.run {
navEmailFolder = navItem
navListener = listener
executePendingBindings()
}
}
}
}

View File

@@ -0,0 +1,45 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.view.View
import androidx.annotation.FloatRange
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.yogeshpaliyal.common.utils.normalize
/**
* An action to be performed when a bottom sheet's slide offset is changed.
*/
interface OnSlideAction {
/**
* Called when the bottom sheet's [slideOffset] is changed. [slideOffset] will always be a
* value between -1.0 and 1.0. -1.0 is equal to [BottomSheetBehavior.STATE_HIDDEN], 0.0
* is equal to [BottomSheetBehavior.STATE_HALF_EXPANDED] and 1.0 is equal to
* [BottomSheetBehavior.STATE_EXPANDED].
*/
fun onSlide(
sheet: View,
@FloatRange(
from = -1.0,
fromInclusive = true,
to = 1.0,
toInclusive = true
) slideOffset: Float
)
}
/**
* Change the alpha of [view] when a bottom sheet is slid.
*
* @param reverse Setting reverse to true will cause the view's alpha to approach 0.0 as the sheet
* slides up. The default behavior, false, causes the view's alpha to approach 1.0 as the sheet
* slides up.
*/
class AlphaSlideAction(
private val view: View,
private val reverse: Boolean = false
) : OnSlideAction {
override fun onSlide(sheet: View, slideOffset: Float) {
val alpha = slideOffset.normalize(-1F, 0F, 0F, 1F)
view.alpha = if (!reverse) alpha else 1F - alpha
}
}

View File

@@ -0,0 +1,90 @@
package com.yogeshpaliyal.keypasscompose.ui.nav
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.floatingactionbutton.FloatingActionButton
/**
* An action to be performed when a bottom sheet's state is changed.
*/
interface OnStateChangedAction {
fun onStateChanged(sheet: View, newState: Int)
}
/**
* A state change action that tells the calling client when a open-sheet specific menu should be
* used.
*/
class ChangeSettingsMenuStateAction(
private val onShouldShowSettingsMenu: (showSettings: Boolean) -> Unit
) : OnStateChangedAction {
private var hasCalledShowSettingsMenu: Boolean = false
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
hasCalledShowSettingsMenu = false
onShouldShowSettingsMenu(false)
} else {
if (!hasCalledShowSettingsMenu) {
hasCalledShowSettingsMenu = true
onShouldShowSettingsMenu(true)
}
}
}
}
/**
* A state change action that handles showing the fab when the sheet is hidden and hiding the fab
* when the sheet is not hidden.
*/
class ShowHideFabStateAction(
private val fab: FloatingActionButton
) : OnStateChangedAction {
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
fab.show()
} else {
fab.hide()
}
}
}
/**
* A state change action that sets a view's visibility depending on whether the sheet is hidden
* or not.
*
* By default, the view will be hidden when the sheet is hidden and shown when the sheet is shown
* (not hidden). If [reverse] is set to true, the view will be shown when the sheet is hidden and
* hidden when the sheet is shown (not hidden).
*/
class VisibilityStateAction(
private val view: View,
private val reverse: Boolean = false
) : OnStateChangedAction {
override fun onStateChanged(sheet: View, newState: Int) {
val stateHiddenVisibility = if (!reverse) View.GONE else View.VISIBLE
val stateDefaultVisibility = if (!reverse) View.VISIBLE else View.GONE
when (newState) {
BottomSheetBehavior.STATE_HIDDEN -> view.visibility = stateHiddenVisibility
else -> view.visibility = stateDefaultVisibility
}
}
}
/**
* A state change action which scrolls a [RecyclerView] to the top when the sheet is hidden.
*
* This is used to make sure the navigation drawer's [RecyclerView] is never half-scrolled when
* opened to the half-expanded state, which can happen if the sheet is hidden while scrolled.
*/
class ScrollToTopStateAction(
private val recyclerView: RecyclerView
) : OnStateChangedAction {
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) recyclerView.scrollToPosition(0)
}
}

View File

@@ -0,0 +1,203 @@
package com.yogeshpaliyal.keypasscompose.ui.settings
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.common.dbhelper.createBackup
import com.yogeshpaliyal.common.dbhelper.restoreBackup
import com.yogeshpaliyal.common.utils.email
import com.yogeshpaliyal.common.utils.getOrCreateBackupKey
import com.yogeshpaliyal.common.utils.setBackupDirectory
import com.yogeshpaliyal.keypasscompose.BuildConfig
import com.yogeshpaliyal.keypasscompose.R
import com.yogeshpaliyal.keypasscompose.databinding.LayoutBackupKeypharseBinding
import com.yogeshpaliyal.keypasscompose.databinding.LayoutRestoreKeypharseBinding
import com.yogeshpaliyal.keypasscompose.ui.backup.BackupActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
private const val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
private const val CHOOSE_RESTORE_FILE_REQUEST_CODE = 26213
@AndroidEntryPoint
class MySettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var appDb: com.yogeshpaliyal.common.AppDatabase
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
"feedback" -> {
context?.email(
getString(R.string.feedback_to_keypass),
"yogeshpaliyal.foss@gmail.com"
)
true
}
"backup" -> {
BackupActivity.start(context)
true
}
getString(R.string.settings_restore_backup) -> {
selectRestoreFile()
true
}
"share" -> {
val sendIntent = Intent()
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(
Intent.EXTRA_TEXT,
"KeyPass Password Manager\n Offline, Secure, Open Source https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID
)
sendIntent.type = "text/plain"
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_keypass)))
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
private fun selectRestoreFile() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
intent.addFlags(
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
try {
startActivityForResult(intent, CHOOSE_RESTORE_FILE_REQUEST_CODE)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val contentResolver = context?.contentResolver
val selectedDirectory = data?.data
if (contentResolver != null && selectedDirectory != null) {
contentResolver.takePersistableUriPermission(
selectedDirectory,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
lifecycleScope.launch {
context?.setBackupDirectory(selectedDirectory.toString())
backup(selectedDirectory)
}
}
} else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val contentResolver = context?.contentResolver
val selectedFile = data?.data
if (contentResolver != null && selectedFile != null) {
val binding = LayoutRestoreKeypharseBinding.inflate(layoutInflater)
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setNegativeButton(
"Cancel"
) { dialog, which ->
dialog.dismiss()
}
.setPositiveButton(
"Restore"
) { dialog, which ->
lifecycleScope.launch {
val result = appDb.restoreBackup(
binding.etKeyPhrase.text.toString(),
contentResolver,
selectedFile
)
if (result) {
dialog?.dismiss()
Toast.makeText(
context,
getString(R.string.backup_restored),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
getString(R.string.invalid_keyphrase),
Toast.LENGTH_SHORT
).show()
}
}
}.show()
}
}
}
suspend fun backup(selectedDirectory: Uri) {
val keyPair = requireContext().getOrCreateBackupKey()
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
"*/*",
"key_pass_backup_${System.currentTimeMillis()}.keypass"
)
lifecycleScope.launch {
context?.contentResolver?.let {
appDb.createBackup(
keyPair.second,
it,
tempFile?.uri
)
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = requireContext().getOrCreateBackupKey().second
binding.txtCode.setOnClickListener {
val clipboard =
getSystemService(requireContext(), ClipboardManager::class.java)
val clip = ClipData.newPlainText(
getString(R.string.app_name),
binding.txtCode.text
)
clipboard?.setPrimaryClip(clip)
Toast.makeText(
context,
getString(R.string.copied_to_clipboard),
Toast.LENGTH_SHORT
).show()
}
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
"Yes"
) { dialog, which ->
dialog?.dismiss()
}.show()
} else {
Toast.makeText(
context,
getString(R.string.backup_completed),
Toast.LENGTH_SHORT
).show()
}
}
}
}
}

View File

@@ -1,33 +0,0 @@
package com.yogeshpaliyal.keypasscompose.ui.theme
import androidx.compose.ui.graphics.Color
@Suppress
val KeyPassWhite50 = Color(0xFFffffff)
val KeyPassBlack800 = Color(0xFF121212)
val KeyPassBlack900 = Color(0xFF000000)
val KeyPassBlue50 = Color(0xFFeef0f2)
val KeyPassBlue100 = Color(0xFFd2dbe0)
val KeyPassBlue200 = Color(0xFFadbbc4)
val KeyPassBlue300 = Color(0xFF8ca2ae)
val KeyPassBlue600 = Color(0xFF4a6572)
val KeyPassBlue700 = Color(0xFF344955)
val KeyPassBlue800 = Color(0xFF232f34)
val Blue800 = Color(0xFF6c63ff)
val KeyPassOrange300 = Color(0xFFfbd790)
val KeyPassOrange400 = Color(0xFFf9be64)
val KeyPassOrange500 = Color(0xFFf9aa33)
val KeyPassRed200 = Color(0xFFcf7779)
val KeyPassRed400 = Color(0xFFff4c5d)
val KeyPassWhite50alpha060 = Color(0x99ffffff)
val KeyPassBlue50alpha060 = Color(0x99eef0f2)
val KeyPassBlack900alpha020 = Color(0x33000000)
val KeyPassBlack900alpha087 = Color(0xde000000)
val KeyPassBlack900alpha060 = Color(0x99000000)

View File

@@ -1,70 +0,0 @@
package com.yogeshpaliyal.keypasscompose.ui.theme
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.BottomAppBar
import androidx.compose.material.DrawerDefaults
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
@Composable
fun Material3BottomAppBar(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(backgroundColor),
cutoutShape: Shape? = null,
elevation: Dp = AppBarDefaults.BottomAppBarElevation,
contentPadding: PaddingValues = AppBarDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
) {
BottomAppBar(
modifier,
backgroundColor,
contentColor,
cutoutShape,
elevation,
contentPadding,
content
)
}
@Composable
fun Material3Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = androidx.compose.material.MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colorScheme.surface,
drawerContentColor: Color = contentColorFor(
drawerBackgroundColor
),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(
backgroundColor
),
content: @Composable (PaddingValues) -> Unit
) {
Scaffold(modifier, scaffoldState, topBar, bottomBar, snackbarHost, floatingActionButton, floatingActionButtonPosition, isFloatingActionButtonDocked, drawerContent, drawerGesturesEnabled, drawerShape, drawerElevation, drawerBackgroundColor, drawerContentColor, drawerScrimColor, backgroundColor, contentColor, content)
}

View File

@@ -1 +0,0 @@
package com.yogeshpaliyal.keypasscompose.ui.theme

View File

@@ -1,51 +0,0 @@
package com.yogeshpaliyal.keypasscompose.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColorScheme(
primary = KeyPassBlue700,
primaryContainer = KeyPassOrange500,
secondary = KeyPassOrange500
)
private val LightColorPalette = lightColorScheme(
primary = KeyPassBlue700,
primaryContainer = KeyPassOrange500,
secondary = KeyPassOrange500,
tertiaryContainer = Blue800,
background = KeyPassBlue50,
surface = KeyPassWhite50,
error = KeyPassRed400,
onPrimary = KeyPassWhite50,
onSecondary = KeyPassBlack900,
onSurface = KeyPassBlack900,
onError = KeyPassBlack900
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
@Composable
fun KeyPassTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colorScheme = colors,
typography = Typography,
content = content
)
}

View File

@@ -1,28 +0,0 @@
package com.yogeshpaliyal.keypasscompose.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
/* Other default text styles to override
button = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)

View File

@@ -0,0 +1,219 @@
/*
* Copyright (c) 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License
*
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yogeshpaliyal.keypasscompose.utils
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.Spinner
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.view.updateLayoutParams
import androidx.databinding.BindingAdapter
import com.google.android.material.elevation.ElevationOverlayProvider
import com.yogeshpaliyal.common.utils.getDrawableOrNull
@BindingAdapter(
"popupElevationOverlay"
)
fun Spinner.bindPopupElevationOverlay(popupElevationOverlay: Float) {
setPopupBackgroundDrawable(
ColorDrawable(
ElevationOverlayProvider(context)
.compositeOverlayWithThemeSurfaceColorIfNeeded(popupElevationOverlay)
)
)
}
@BindingAdapter(
"drawableStart",
"drawableLeft",
"drawableTop",
"drawableEnd",
"drawableRight",
"drawableBottom",
requireAll = false
)
fun TextView.bindDrawables(
@DrawableRes drawableStart: Int? = null,
@DrawableRes drawableLeft: Int? = null,
@DrawableRes drawableTop: Int? = null,
@DrawableRes drawableEnd: Int? = null,
@DrawableRes drawableRight: Int? = null,
@DrawableRes drawableBottom: Int? = null
) {
setCompoundDrawablesWithIntrinsicBounds(
context.getDrawableOrNull(drawableStart ?: drawableLeft),
context.getDrawableOrNull(drawableTop),
context.getDrawableOrNull(drawableEnd ?: drawableRight),
context.getDrawableOrNull(drawableBottom)
)
}
@BindingAdapter("goneIf")
fun View.bindGoneIf(gone: Boolean) {
visibility = if (gone) {
GONE
} else {
VISIBLE
}
}
@BindingAdapter("layoutFullscreen")
fun View.bindLayoutFullscreen(previousFullscreen: Boolean, fullscreen: Boolean) {
if (previousFullscreen != fullscreen && fullscreen) {
systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
}
}
@BindingAdapter(
"paddingLeftSystemWindowInsets",
"paddingTopSystemWindowInsets",
"paddingRightSystemWindowInsets",
"paddingBottomSystemWindowInsets",
requireAll = false
)
fun View.applySystemWindowInsetsPadding(
previousApplyLeft: Boolean,
previousApplyTop: Boolean,
previousApplyRight: Boolean,
previousApplyBottom: Boolean,
applyLeft: Boolean,
applyTop: Boolean,
applyRight: Boolean,
applyBottom: Boolean
) {
if (previousApplyLeft == applyLeft &&
previousApplyTop == applyTop &&
previousApplyRight == applyRight &&
previousApplyBottom == applyBottom
) {
return
}
doOnApplyWindowInsets { view, insets, padding, _, _ ->
val left = if (applyLeft) insets.systemWindowInsetLeft else 0
val top = if (applyTop) insets.systemWindowInsetTop else 0
val right = if (applyRight) insets.systemWindowInsetRight else 0
val bottom = if (applyBottom) insets.systemWindowInsetBottom else 0
view.setPadding(
padding.left + left,
padding.top + top,
padding.right + right,
padding.bottom + bottom
)
}
}
@BindingAdapter(
"marginLeftSystemWindowInsets",
"marginTopSystemWindowInsets",
"marginRightSystemWindowInsets",
"marginBottomSystemWindowInsets",
requireAll = false
)
fun View.applySystemWindowInsetsMargin(
previousApplyLeft: Boolean,
previousApplyTop: Boolean,
previousApplyRight: Boolean,
previousApplyBottom: Boolean,
applyLeft: Boolean,
applyTop: Boolean,
applyRight: Boolean,
applyBottom: Boolean
) {
if (previousApplyLeft == applyLeft &&
previousApplyTop == applyTop &&
previousApplyRight == applyRight &&
previousApplyBottom == applyBottom
) {
return
}
doOnApplyWindowInsets { view, insets, _, margin, _ ->
val left = if (applyLeft) insets.systemWindowInsetLeft else 0
val top = if (applyTop) insets.systemWindowInsetTop else 0
val right = if (applyRight) insets.systemWindowInsetRight else 0
val bottom = if (applyBottom) insets.systemWindowInsetBottom else 0
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = margin.left + left
topMargin = margin.top + top
rightMargin = margin.right + right
bottomMargin = margin.bottom + bottom
}
}
}
fun View.doOnApplyWindowInsets(
block: (View, WindowInsets, InitialPadding, InitialMargin, Int) -> Unit
) {
// Create a snapshot of the view's padding & margin states
val initialPadding = recordInitialPaddingForView(this)
val initialMargin = recordInitialMarginForView(this)
val initialHeight = recordInitialHeightForView(this)
// Set an actual OnApplyWindowInsetsListener which proxies to the given
// lambda, also passing in the original padding & margin states
setOnApplyWindowInsetsListener { v, insets ->
block(v, insets, initialPadding, initialMargin, initialHeight)
// Always return the insets, so that children can also use them
insets
}
// request some insets
requestApplyInsetsWhenAttached()
}
class InitialPadding(val left: Int, val top: Int, val right: Int, val bottom: Int)
class InitialMargin(val left: Int, val top: Int, val right: Int, val bottom: Int)
private fun recordInitialPaddingForView(view: View) = InitialPadding(
view.paddingLeft, view.paddingTop, view.paddingRight, view.paddingBottom
)
private fun recordInitialMarginForView(view: View): InitialMargin {
val lp = view.layoutParams as? ViewGroup.MarginLayoutParams
?: throw IllegalArgumentException("Invalid view layout params")
return InitialMargin(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin)
}
private fun recordInitialHeightForView(view: View): Int {
return view.layoutParams.height
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
// We're already attached, just request as normal
requestApplyInsets()
} else {
// We're not attached to the hierarchy, add a listener to
// request when we are
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.removeOnAttachStateChangeListener(this)
v.requestApplyInsets()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yogeshpaliyal.keypasscompose.utils
import android.app.Activity
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import kotlin.reflect.KProperty
/**
* A delegate who lazily inflates a data binding layout, calls [Activity.setContentView], sets
* the lifecycle owner and returns the binding.
*/
class ContentViewBindingDelegate<in R : AppCompatActivity, out T : ViewDataBinding>(
@LayoutRes private val layoutRes: Int
) {
private var binding: T? = null
operator fun getValue(activity: R, property: KProperty<*>): T {
if (binding == null) {
binding = DataBindingUtil.setContentView<T>(activity, layoutRes).apply {
lifecycleOwner = activity
}
}
return binding!!
}
}
fun <R : AppCompatActivity, T : ViewDataBinding> contentView(
@LayoutRes layoutRes: Int
): ContentViewBindingDelegate<R, T> = ContentViewBindingDelegate(layoutRes)

View File

@@ -0,0 +1,49 @@
package com.yogeshpaliyal.keypasscompose.utils
import android.util.Log
import com.yogeshpaliyal.keypasscompose.BuildConfig
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 26-12-2020 20:21
*/
@JvmName("LogHelper")
fun Any?.systemOutPrint() {
if (BuildConfig.DEBUG) println(this)
}
fun Any?.systemErrPrint() {
if (BuildConfig.DEBUG) System.err.println(this)
}
fun Exception?.debugPrintStackTrace() {
if (BuildConfig.DEBUG) this?.printStackTrace()
}
fun Throwable?.debugPrintStackTrace() {
if (BuildConfig.DEBUG) this?.printStackTrace()
}
fun Any?.logD(tag: String?) {
if (BuildConfig.DEBUG) Log.d(tag, this.toString())
}
fun Any?.logE(tag: String?) {
if (BuildConfig.DEBUG) Log.e(tag, this.toString())
}
fun Any?.logI(tag: String?) {
if (BuildConfig.DEBUG) Log.i(tag, this.toString())
}
fun Any?.logV(tag: String?) {
if (BuildConfig.DEBUG) Log.v(tag, this.toString())
}
fun Any?.logW(tag: String?) {
if (BuildConfig.DEBUG) Log.w(tag, this.toString())
}

View File

@@ -0,0 +1,12 @@
package com.yogeshpaliyal.keypasscompose.utils
import androidx.recyclerview.widget.DiffUtil
/**
* Alias to represent a folder (a String title) into which emails can be placed.
*/
object StringDiffUtil : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String) = oldItem == newItem
override fun areContentsTheSame(oldItem: String, newItem: String) = oldItem == newItem
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yogeshpaliyal.keypasscompose.utils
import android.content.Context
import android.os.Build
import android.widget.TextView
@Suppress("DEPRECATION")
fun TextView.setTextAppearanceCompat(context: Context, resId: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setTextAppearance(resId)
} else {
setTextAppearance(context, resId)
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
<item android:color="?attr/colorOnSurface" />
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnSurface" android:alpha="0.2" />
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnPrimarySurface" android:alpha="?attr/emphasisMediumAlpha" />
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnSurface" android:alpha="?attr/emphasisHighAlpha" />
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnSurface" android:alpha="?attr/emphasisMediumAlpha" />
</selector>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add"
android:drawable="@drawable/ic_round_add_24"
android:state_activated="false"/>
<item
android:id="@+id/save"
android:drawable="@drawable/ic_round_done_24"
android:state_activated="true"/>
<transition
android:fromId="@id/add"
android:toId="@id/save"
android:drawable="@drawable/avd_add_to_save"/>
<transition
android:fromId="@id/save"
android:toId="@id/add"
android:drawable="@drawable/avd_save_to_add"/>
</animated-selector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_on_primary_surface_divider" />
</shape>

View File

@@ -0,0 +1,126 @@
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group android:name="group">
<path
android:fillColor="#000"
android:name="path"
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
</group>
<group android:name="group_1">
<path
android:name="path_1"
android:fillColor="#000"
android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z"/>
</group>
</vector>
</aapt:attr>
<target android:name="group">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="scaleX"
android:duration="100"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="scaleY"
android:duration="100"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="translateY"
android:duration="300"
android:valueFrom="-0.8"
android:valueTo="-0.8"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="rotation"
android:duration="100"
android:valueFrom="0"
android:valueTo="30"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotX"
android:duration="100"
android:valueFrom="22"
android:valueTo="22"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotY"
android:duration="100"
android:valueFrom="16"
android:valueTo="16"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
<target android:name="group_1">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="scaleX"
android:startOffset="100"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="scaleY"
android:startOffset="100"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotX"
android:startOffset="100"
android:duration="200"
android:valueFrom="12"
android:valueTo="12"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotY"
android:startOffset="100"
android:duration="200"
android:valueFrom="12"
android:valueTo="12"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -0,0 +1,127 @@
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group android:name="group">
<path
android:name="path"
android:fillColor="#000"
android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z"/>
</group>
<group android:name="group_1">
<path
android:name="path_1"
android:fillColor="#000"
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
</group>
</vector>
</aapt:attr>
<target android:name="group">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="scaleX"
android:duration="100"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="scaleY"
android:duration="100"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="translateY"
android:duration="300"
android:valueFrom="-0.8"
android:valueTo="-0.8"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="rotation"
android:duration="100"
android:valueFrom="0"
android:valueTo="30"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotX"
android:duration="100"
android:valueFrom="22"
android:valueTo="22"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotY"
android:duration="100"
android:valueFrom="16"
android:valueTo="16"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
<target android:name="group_1">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="scaleX"
android:startOffset="100"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="scaleY"
android:startOffset="100"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotX"
android:startOffset="100"
android:duration="200"
android:valueFrom="12"
android:valueTo="12"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
<objectAnimator
android:propertyName="pivotY"
android:startOffset="100"
android:duration="200"
android:valueFrom="12"
android:valueTo="12"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M11.67,3.87L9.9,2.1 0,12l9.9,9.9 1.77,-1.77L3.54,12z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorSecondary">
<path
android:fillColor="@color/keypass_orange_500"
android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM7.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5S6.67,15 7.5,15s1.5,0.67 1.5,1.5S8.33,18 7.5,18zM7.5,9C6.67,9 6,8.33 6,7.5S6.67,6 7.5,6 9,6.67 9,7.5 8.33,9 7.5,9zM12,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM16.5,9c-0.83,0 -1.5,-0.67 -1.5,-1.5S15.67,6 16.5,6s1.5,0.67 1.5,1.5S17.33,9 16.5,9z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM13,14h-2v-2h2v2zM13,10h-2L11,6h2v4z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4,18h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,16c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM4,13h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM3,7c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,6c-0.55,0 -1,0.45 -1,1z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17.65,6.35c-1.63,-1.63 -3.94,-2.57 -6.48,-2.31 -3.67,0.37 -6.69,3.35 -7.1,7.02C3.52,15.91 7.27,20 12,20c3.19,0 5.93,-1.87 7.21,-4.56 0.32,-0.67 -0.16,-1.44 -0.9,-1.44 -0.37,0 -0.72,0.2 -0.88,0.53 -1.13,2.43 -3.84,3.97 -6.8,3.31 -2.22,-0.49 -4.01,-2.3 -4.48,-4.52C5.31,9.44 8.26,6 12,6c1.66,0 3.14,0.69 4.22,1.78l-1.51,1.51c-0.63,0.63 -0.19,1.71 0.7,1.71H19c0.55,0 1,-0.45 1,-1V6.41c0,-0.89 -1.08,-1.34 -1.71,-0.71l-0.64,0.65z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8,7h11v14H8z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8,9h8v10H8z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,4l-1,-1h-5l-1,1H5v2h14V4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM8,9h8v10H8V9z"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11.17,8l-0.58,-0.59L9.17,6H4v12h16V8h-8z"
android:fillAlpha="0.3"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h5.17l1.41,1.41 0.59,0.59L20,8v10z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3zM17,18h-2v-6L9,12v6L7,18v-7.81l5,-4.5 5,4.5L17,18z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M7,10.19V18h2v-6h6v6h2v-7.81l-5,-4.5z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9.5,6.5v3h-3v-3H9.5M11,5H5v6h6V5L11,5zM9.5,14.5v3h-3v-3H9.5M11,13H5v6h6V13L11,13zM17.5,6.5v3h-3v-3H17.5M19,5h-6v6h6V5L19,5zM13,13h1.5v1.5H13V13zM14.5,14.5H16V16h-1.5V14.5zM16,13h1.5v1.5H16V13zM13,16h1.5v1.5H13V16zM14.5,17.5H16V19h-1.5V17.5zM16,16h1.5v1.5H16V16zM17.5,14.5H19V16h-1.5V14.5zM17.5,17.5H19V19h-1.5V17.5zM22,7h-2V4h-3V2h5V7zM22,22v-5h-2v3h-3v2H22zM2,22h5v-2H4v-3H2V22zM2,2v5h2V4h3V2H2z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11.71,10.33C11.01,8.34 9.11,7 7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5c2.11,0 4.01,-1.34 4.71,-3.33l0.23,-0.67H18v4h2v-4h2v-2H11.94l-0.23,-0.67zM7,15c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3 3,1.35 3,3 -1.35,3 -3,3z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M7,5c-3.86,0 -7,3.14 -7,7s3.14,7 7,7c2.72,0 5.17,-1.58 6.32,-4L16,15v4h6v-4h2L24,9L13.32,9C12.17,6.58 9.72,5 7,5zM22,13h-2v4h-2v-4h-6.06l-0.23,0.67C11.01,15.66 9.11,17 7,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5c2.11,0 4.01,1.34 4.71,3.33l0.23,0.67L22,11v2zM7,9c-1.65,0 -3,1.35 -3,3s1.35,3 3,3 3,-1.35 3,-3 -1.35,-3 -3,-3zM7,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
</vector>

View File

@@ -0,0 +1,204 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="997.8612dp"
android:height="450.8081dp"
android:viewportWidth="997.8612"
android:viewportHeight="450.8081">
<path
android:pathData="M871.992,181.558h30.159v104.398h-30.159z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M922.068,266.317l-73.353,-87.265l-147.24,1.346l-89.319,86.998l1.805,1.16l-0.645,0l0,180.957l308.555,0l0,-180.957l0.197,-2.239z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M848.792,179.238l-91.638,107.436l0,162.839l164.717,0l0,-183.277l-73.079,-86.998z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M823.272,359.461h33.639v29.733h-33.639z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M823.272,307.996h33.639v29.262h-33.639z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M823.272,359.461h33.639v29.733h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M823.272,307.996h33.639v29.262h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M673.777,351.571h33.639v29.733h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M673.777,300.106h33.639v29.262h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M633.992,181.558h30.159v104.398h-30.159z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M684.068,266.317l-73.353,-87.265l-147.24,1.346l-89.319,86.998l1.805,1.16l-0.645,0l0,180.957l308.555,0l0,-180.957l0.197,-2.239z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M610.792,179.238l-91.638,107.436l0,162.839l164.717,0l0,-183.277l-73.079,-86.998z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M585.272,359.461h33.639v29.733h-33.639z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M585.272,307.996h33.639v29.262h-33.639z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M585.272,359.461h33.639v29.733h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M585.272,307.996h33.639v29.262h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M435.777,351.571h33.639v29.733h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M435.777,300.106h33.639v29.262h-33.639z"
android:fillColor="#fff"/>
<path
android:pathData="M380.154,91.46h40.3v139.501h-40.3z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M447.068,204.718l-98.017,-116.606l-196.749,1.798l-119.351,116.251l2.411,1.55l-0.861,0l0,241.802l412.303,0l0,-241.802l0.264,-2.993z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M349.153,88.36l-122.451,143.561l0,217.592l220.102,0l0,-244.902l-97.651,-116.251z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M315.053,329.181h44.95v39.731h-44.95z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M315.053,260.412h44.95v39.101h-44.95z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M315.053,329.181h44.95v39.731h-44.95z"
android:fillColor="#fff"/>
<path
android:pathData="M315.053,260.412h44.95v39.101h-44.95z"
android:fillColor="#fff"/>
<path
android:pathData="M115.29,318.639h44.95v39.731h-44.95z"
android:fillColor="#fff"/>
<path
android:pathData="M115.29,249.869h44.95v39.101h-44.95z"
android:fillColor="#fff"/>
<path
android:pathData="M0,448.62h963.951v2h-963.951z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M122.773,352.472a29.099,59.374 0,1 0,58.199 0a29.099,59.374 0,1 0,-58.199 0z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M154.559,449.658c-11.655,-69.925 -0.117,-139.598 0.001,-140.293l2.267,0.384c-0.117,0.692 -11.588,69.998 0.001,139.532Z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M151.178,353.78l26.314,-14.075l1.085,2.028l-26.314,14.075z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M123.406,348.783l1.085,-2.028l26.311,14.083l-1.085,2.028z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M25.04,260.903a56.915,116.129 0,1 0,113.83 0a56.915,116.129 0,1 0,-113.83 0z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M88.295,450.808c-22.765,-136.585 -0.23,-272.673 0.001,-274.032l2.267,0.384c-0.23,1.355 -22.698,137.077 0.001,273.271Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M81.115,264.43l51.468,-27.529l1.085,2.028l-51.468,27.529z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M26.797,252.719l1.085,-2.028l51.46,27.543l-1.085,2.028z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M138.87,191.008a77.883,158.914 0,1 0,155.767 0a77.883,158.914 0,1 0,-155.767 0z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M225.847,450.808c-31.14,-186.837 -0.314,-372.992 0.001,-374.851l2.267,0.384c-0.314,1.855 -31.074,187.644 0.001,374.089Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M215.803,196.207l70.429,-37.671l1.085,2.028l-70.429,37.671z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M141.474,179.435l1.085,-2.028l70.419,37.69l-1.085,2.028z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M841.93,352.472a29.099,59.374 0,1 0,58.199 0a29.099,59.374 0,1 0,-58.199 0z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M868.342,449.658c11.655,-69.925 0.117,-139.598 -0.001,-140.293l-2.267,0.384c0.117,0.692 11.588,69.998 -0.001,139.532Z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M844.325,341.733l1.085,-2.028l26.314,14.075l-1.085,2.028z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M872.1,360.838l26.311,-14.083l1.085,2.028l-26.311,14.083z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M884.031,260.903a56.915,116.129 0,1 0,113.83 0a56.915,116.129 0,1 0,-113.83 0z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M934.607,450.808c22.765,-136.585 0.23,-272.673 -0.001,-274.032l-2.267,0.384c0.23,1.355 22.698,137.077 -0.001,273.271Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M889.235,238.928l1.085,-2.028l51.468,27.529l-1.085,2.028z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M943.559,278.235l51.46,-27.543l1.085,2.028l-51.46,27.543z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M728.265,191.008a77.883,158.914 0,1 0,155.767 0a77.883,158.914 0,1 0,-155.767 0z"
android:fillColor="?attr/colorTertiaryContainer"/>
<path
android:pathData="M797.055,450.808c31.14,-186.837 0.314,-372.992 -0.001,-374.851l-2.267,0.384c0.314,1.855 31.074,187.644 -0.001,374.089Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M735.584,160.563l1.085,-2.028l70.429,37.671l-1.085,2.028z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M809.923,215.097l70.419,-37.69l1.085,2.028l-70.419,37.69z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M589.604,101.466l9.206,-7.363c-7.151,-0.789 -10.09,3.111 -11.292,6.198 -5.587,-2.32 -11.669,0.72 -11.669,0.72l18.419,6.687A13.938,13.938 0,0 0,589.604 101.466Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M364.604,36.466l9.206,-7.363c-7.151,-0.789 -10.09,3.111 -11.292,6.198 -5.587,-2.32 -11.669,0.72 -11.669,0.72l18.419,6.687A13.938,13.938 0,0 0,364.604 36.466Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M731.604,7.466l9.206,-7.363c-7.151,-0.789 -10.09,3.111 -11.292,6.198 -5.587,-2.32 -11.669,0.72 -11.669,0.72l18.419,6.687A13.938,13.938 0,0 0,731.604 7.466Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M750.191,437.052a13.918,13.918 0,0 0,-6.97 1.87A14.982,14.982 0,0 0,718.191 450.052h45.95A13.99,13.99 0,0 0,750.191 437.052Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M283.191,437.052a13.918,13.918 0,0 0,-6.97 1.87A14.982,14.982 0,0 0,251.191 450.052h45.95A13.99,13.99 0,0 0,283.191 437.052Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M522.191,437.052a13.918,13.918 0,0 0,-6.97 1.87A14.982,14.982 0,0 0,490.191 450.052h45.95A13.99,13.99 0,0 0,522.191 437.052Z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M471.759,404.228l-132.568,0l0,4.276l20.675,0l0,40.626l4.276,0l0,-40.626l80.527,0l0,40.626l4.277,0l0,-40.626l22.813,0l0,-4.276z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M339.452,391.434h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M339.452,380.743h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M339.452,370.052h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M678.759,404.228l-132.568,0l0,4.276l20.675,0l0,40.626l4.276,0l0,-40.626l80.527,0l0,40.626l4.277,0l0,-40.626l22.813,0l0,-4.276z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M546.452,391.434h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M546.452,380.743h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
<path
android:pathData="M546.452,370.052h132.568v4.276h-132.568z"
android:fillColor="?attr/colorPrimary"/>
</vector>

View File

@@ -0,0 +1,194 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1010.89166dp"
android:height="727.2841dp"
android:viewportWidth="1010.89166"
android:viewportHeight="727.2841">
<path
android:pathData="M1010.89,727.28q-78.13,-11.44 -150.28,-19.73l19.02,-30.78c-6.41,-1.89 -34.58,15.65 -34.58,15.65l24.97,-89.48c-32.25,3.11 -48.66,94.51 -48.66,94.51L785.33,666.36l17.44,34.85c-147.21,-15.2 -274.35,-21.16 -381.29,-21.85L437.76,653.02c-6.41,-1.89 -34.58,15.65 -34.58,15.65l24.97,-89.48c-32.25,3.11 -48.66,94.51 -48.66,94.51L343.46,642.61l18.47,36.92c-89.65,1.09 -162.76,5.87 -219.19,11.45 16.47,-41.08 72.39,-79.96 72.39,-79.96 -42.71,10.44 -65.11,28.15 -76.82,44.71 -0.89,-50.82 5.13,-145 49.86,-249.09 0,0 -88.3,160.41 -77.05,268.41l1.34,19.17c-74.84,8.62 -112.46,17.78 -112.46,17.78Z"
android:fillColor="#3f3d56"/>
<path
android:pathData="M537.53,29.13v648.2a29.14,29.14 0,0 1,-29.13 29.11h-263.15a29.08,29.08 0,0 1,-29.13 -29.11v-648.2a29.13,29.13 0,0 1,29.13 -29.13h39.38v5.05a23.99,23.99 0,0 0,23.98 23.99h133.84a23.79,23.79 0,0 0,14.22 -4.68,22.28 22.28,0 0,0 2.36,-1.98 23.92,23.92 0,0 0,7.4 -17.33v-5.05h41.97A29.12,29.12 0,0 1,537.53 29.13Z"
android:fillColor="#3f3d56"/>
<path
android:pathData="M289.43,108m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M459.03,22.38a22.28,22.28 0,0 1,-2.36 1.98,125.25 125.25,0 0,0 80.86,60.19v-3.12A122.3,122.3 0,0 1,459.03 22.38Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M399.22,29.04h-3.28a181.67,181.67 0,0 0,141.59 113.05v-3.1A178.55,178.55 0,0 1,399.22 29.04Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M338.8,29.04h-3.16c26.43,91.74 106.18,160.03 201.89,171.12v-3.05C443.48,186.07 365.1,119.08 338.8,29.04Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M266.43,159.82l-1.82,0l0,-1.82l-0.36,0l0,1.82l-1.82,0l0,0.36l1.82,0l0,1.82l0.36,0l0,-1.82l1.82,0l0,-0.36z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M389.43,68.82l-1.82,0l0,-1.82l-0.36,0l0,1.82l-1.82,0l0,0.36l1.82,0l0,1.82l0.36,0l0,-1.82l1.82,0l0,-0.36z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M284.43,231.82l-1.82,0l0,-1.82l-0.36,0l0,1.82l-1.82,0l0,0.36l1.82,0l0,1.82l0.36,0l0,-1.82l1.82,0l0,-0.36z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M368.43,178.82l-1.82,0l0,-1.82l-0.36,0l0,1.82l-1.82,0l0,0.36l1.82,0l0,1.82l0.36,0l0,-1.82l1.82,0l0,-0.36z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M488.43,211.82l-1.82,0l0,-1.82l-0.36,0l0,1.82l-1.82,0l0,0.36l1.82,0l0,1.82l0.36,0l0,-1.82l1.82,0l0,-0.36z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M467.43,125m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:fillColor="#ff6584"/>
<path
android:pathData="M324.13,218.8l-2.27,0.35c0.71,4.54 1.27,9.18 1.68,13.77l2.29,-0.2C325.42,228.07 324.85,223.39 324.13,218.8Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M317.92,191.46l-2.2,0.67c1.33,4.4 2.52,8.91 3.53,13.4l2.25,-0.51C320.47,200.48 319.26,195.92 317.92,191.46Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M308.01,165.24l-2.09,0.96c1.92,4.19 3.72,8.49 5.35,12.79l2.15,-0.81C311.77,173.83 309.96,169.47 308.01,165.24Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M294.61,140.66l-1.94,1.23c2.47,3.88 4.84,7.9 7.04,11.93l2.02,-1.1C299.51,148.64 297.11,144.58 294.61,140.66Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M277.95,118.13l-1.75,1.49c2.98,3.5 5.88,7.16 8.62,10.86l1.84,-1.37C283.9,125.37 280.97,121.67 277.95,118.13Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M258.37,98.07l-1.53,1.71c3.44,3.07 6.81,6.3 10.02,9.59l1.65,-1.61C265.26,104.43 261.85,101.17 258.37,98.07Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M236.25,80.85l-1.29,1.9c3.82,2.57 7.6,5.31 11.23,8.14l1.42,-1.81C243.93,86.22 240.11,83.45 236.25,80.85Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M216.12,68.89v2.6c2.39,1.25 4.76,2.56 7.08,3.9l1.15,-1.99Q220.3,71.06 216.12,68.89Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M323.61,434.8c0.37,-2.24 0.71,-4.52 1.01,-6.79l2.28,0.3c-0.3,2.29 -0.65,4.6 -1.03,6.86Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M317.2,461.78l2.2,0.68c1.38,-4.44 2.63,-9 3.71,-13.53l-2.24,-0.53C319.79,452.89 318.57,457.39 317.2,461.78Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M307.16,487.61l2.09,0.98c1.98,-4.21 3.84,-8.54 5.52,-12.88l-2.15,-0.84C310.96,479.16 309.12,483.45 307.16,487.61Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M293.64,511.85l1.93,1.25c2.54,-3.9 4.98,-7.94 7.25,-12.01l-2.01,-1.12C298.58,503.99 296.16,507.98 293.64,511.85Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M276.93,533.99l1.74,1.51c3.06,-3.53 6.03,-7.2 8.84,-10.91l-1.84,-1.38C282.89,526.88 279.96,530.5 276.93,533.99Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M257.35,553.63l1.51,1.73c3.52,-3.07 6.95,-6.3 10.24,-9.59l-1.63,-1.63C264.23,547.39 260.82,550.59 257.35,553.63Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M235.3,570.42l1.27,1.92c3.88,-2.56 7.74,-5.3 11.44,-8.11l-1.39,-1.84C242.95,565.19 239.15,567.89 235.3,570.42Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M224.61,579.65c-2.78,1.57 -5.62,3.09 -8.49,4.53v-2.58c2.48,-1.26 4.94,-2.59 7.35,-3.94Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M324.28,247.11l2.3,-0.04c0.04,2.32 0.05,4.65 0.01,6.94l-2.3,-0.04C324.33,251.71 324.32,249.4 324.28,247.11Z"
android:fillColor="#e6e6e6"/>
<path
android:pathData="M226.22,301.63H461.43a66,66 0,0 1,66 66v8.74a0,0 0,0 1,0 0H292.22a66,66 0,0 1,-66 -66v-8.74A0,0 0,0 1,226.22 301.63Z"
android:fillColor="#fff"/>
<path
android:pathData="M382.32,705.52c-0.23,-0.38 -5.64,-9.41 -7.52,-28.17 -1.72,-17.21 -0.61,-46.23 14.43,-86.7 28.51,-76.67 -6.57,-138.53 -6.93,-139.15l1.73,-1c0.09,0.16 9.14,15.93 14.49,41.04a179.06,179.06 0,0 1,-7.42 99.81c-28.46,76.54 -7.3,112.77 -7.08,113.13Z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M371.17,434m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0"
android:fillColor="#6c63ff"/>
<path
android:pathData="M412.17,482m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M384.17,514m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0"
android:fillColor="#6c63ff"/>
<path
android:pathData="M418.17,541m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0"
android:fillColor="#6c63ff"/>
<path
android:pathData="M374.17,583m-13,0a13,13 0,1 1,26 0a13,13 0,1 1,-26 0"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M390.17,706s-13,-32 26,-56Z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M374.18,705.42s-5.92,-34.03 -51.71,-33.74Z"
android:fillColor="#f2f2f2"/>
<path
android:pathData="M322.43,339m-80,0a80,80 0,1 1,160 0a80,80 0,1 1,-160 0"
android:strokeAlpha="0.2"
android:fillColor="#6c63ff"
android:fillAlpha="0.2"/>
<path
android:pathData="M322.43,339m-56.34,0a56.34,56.34 0,1 1,112.68 0a56.34,56.34 0,1 1,-112.68 0"
android:strokeAlpha="0.2"
android:fillColor="#6c63ff"
android:fillAlpha="0.2"/>
<path
android:pathData="M322.43,339m-37.18,0a37.18,37.18 0,1 1,74.37 0a37.18,37.18 0,1 1,-74.37 0"
android:fillColor="#6c63ff"/>
<path
android:pathData="M329.19,330.98a6.76,6.76 0,1 0,-11.03 5.24l-2.49,17.43h13.52L326.7,336.22A6.75,6.75 0,0 0,329.19 330.98Z"
android:fillColor="#fff"/>
<path
android:pathData="M696.11,53.3s-22.3,-19.99 -52.29,17.69 -76.9,56.14 -87.67,70.75c0,0 44.6,-18.46 59.98,-26.15s14.61,-6.15 14.61,-6.15 -20.76,14.61 -24.61,29.22 -0.77,26.92 -7.69,41.53S730.71,190.19 730.71,190.19s13.84,-23.84 10,-48.45S739.17,56.38 696.11,53.3Z"
android:fillColor="#2f2e41"/>
<path
android:pathData="M653.92,116.2s4.85,33.95 -15.93,41.57 -13.16,18.01 -13.16,18.01l31.18,15.24 33.25,-10.39 11.08,-16.63s-18.01,-4.16 -13.16,-16.63 6.24,-16.63 6.24,-16.63Z"
android:fillColor="#ffb9b9"/>
<path
android:fillColor="#FF000000"
android:pathData="M653.92,116.2s4.85,33.95 -15.93,41.57 -13.16,18.01 -13.16,18.01l31.18,15.24 33.25,-10.39 11.08,-16.63s-18.01,-4.16 -13.16,-16.63 6.24,-16.63 6.24,-16.63Z"
android:strokeAlpha="0.1"
android:fillAlpha="0.1"/>
<path
android:pathData="M741.22,213.19s-4.85,63.74 -3.46,66.51 0,99.07 0,99.07 9.7,51.96 -2.77,54.73 -9.7,-58.19 -9.7,-58.19L712.81,294.94l1.39,-79.67Z"
android:fillColor="#ffb9b9"/>
<path
android:fillColor="#FF000000"
android:pathData="M741.22,213.19s-4.85,63.74 -3.46,66.51 0,99.07 0,99.07 9.7,51.96 -2.77,54.73 -9.7,-58.19 -9.7,-58.19L712.81,294.94l1.39,-79.67Z"
android:strokeAlpha="0.1"
android:fillAlpha="0.1"/>
<path
android:pathData="M606.12,405.1S583.26,491.69 583.95,533.96 593.65,635.1 593.65,635.1s1.39,25.63 -0.69,28.4S603.35,678.06 603.35,678.06l13.16,-7.62 4.85,-5.54L621.36,660.04s-7.62,-23.55 -1.39,-42.26 9.7,-65.82 3.46,-80.36l42.95,-127.47Z"
android:fillColor="#ffb9b9"/>
<path
android:pathData="M604.74,666.97S596.42,656.58 590.88,660.04s-14.55,27.02 -14.55,27.02 -31.18,29.79 -9.01,31.18 31.87,-6.24 34.64,-11.78S630.37,686.37 630.37,686.37s-2.77,-26.33 -6.93,-27.02S613.05,669.74 613.05,669.74Z"
android:fillColor="#2f2e41"/>
<path
android:pathData="M681.64,410.64v85.91a265.62,265.62 0,0 0,2.08 31.18c2.08,15.24 -11.78,128.86 -11.78,128.86l1.39,20.09 17.32,-1.39 2.08,-17.32 24.94,-76.21s8.31,-46.42 4.16,-63.74 21.48,-128.17 21.48,-128.17Z"
android:fillColor="#ffb9b9"/>
<path
android:pathData="M667.78,671.82l5.13,-1.12s3.88,-8.58 8.72,-6.51a52.3,52.3 0,0 1,9.64 5.78l2.13,3.92s4.85,10.39 11.08,17.32 13.16,22.17 2.77,23.55 -24.25,2.08 -28.4,-1.39 -1.39,-9.01 -5.54,-10.39 -7.62,-5.54 -6.93,-6.93S667.78,671.82 667.78,671.82Z"
android:fillColor="#2f2e41"/>
<path
android:pathData="M681.64,109.27m-30.79,0a30.79,30.79 0,1 1,61.58 0a30.79,30.79 0,1 1,-61.58 0"
android:fillColor="#ffb9b9"/>
<path
android:pathData="M623.44,163.31l9.15,-3.19s-3.61,5.97 15.79,9.43 39.49,4.69 48.49,-6.66c0,0 3.46,-1.65 8.31,3.2s7.62,3.46 7.62,3.46l-3.46,18.01 -6.93,33.25L693.41,245.75l-25.63,-5.54L640.07,220.12 629.68,193.1L629.68,168.16Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M707.96,173.01l6.93,-4.85s23.55,4.16 24.94,15.24L721.82,218.04a39.44,39.44 0,0 1,-0.69 29.79c-6.93,15.93 -6.24,19.4 -6.24,19.4l-4.85,20.78 -87.98,6.93s-3.46,-8.31 -4.85,-9.7 -1.39,-6.24 0,-6.24 0,-2.08 -1.39,-4.16 -2.08,-2.77 0,-5.54 -3.46,-27.71 -3.46,-27.71L612.36,214.58l-23.55,-31.18s8.31,-12.47 12.47,-13.86 23.93,-6.85 23.93,-6.85l4.47,6.59 6.24,50.14 9.01,30.48 37.92,-6.44 14.73,-24.73 9.7,-29.1Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M734.29,179.25l5.54,4.16s4.85,38.8 3.46,41.57 -25.63,5.54 -26.33,4.16S734.29,179.25 734.29,179.25Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M619.98,159.85l6.93,45.72s-6.24,67.89 -4.85,81.75l1.39,4.16s-2.77,-1.39 -3.46,1.39 0,11.78 0,11.78 -4.16,4.16 -5.54,27.02 -18.01,77.59 -11.78,79.67 43.65,12.47 63.74,10.39 92.14,-26.33 91.45,-32.56 -35.33,-94.22 -35.33,-94.22S716.28,284.55 716.28,283.16s8.31,-6.93 6.24,-12.47 -13.16,-63.74 -13.16,-63.74l7.62,-41.57L706.58,162.62l-7.62,40.18s-29.79,14.55 -60.97,1.39l-6.93,-47.11Z"
android:fillColor="#2f2e41"/>
<path
android:pathData="M653.31,87.18s51.27,35.33 65.12,19.4 -24.25,-29.79 -24.25,-29.79l-31.87,-3.46Z"
android:fillColor="#2f2e41"/>
<path
android:pathData="M573.65,463.04a27.03,27.03 0,1 0,-8.48 -10.52L494.47,511.39l-9.41,-11.17 -4.23,2.83 -4.71,-5.59 3.87,-3.26 -4.71,-5.59 -6.02,5.07 2.9,3.44L465.71,502.55l-2.9,-3.44 -9.45,7.97 4.35,5.16 3.44,-2.9 4.71,5.59 -4.3,3.62 8.69,10.31 -10.31,8.69 10.5,12.46ZM591.31,423.55A17.88,17.88 0,1 1,571.97 439.84,17.88 17.88,0 0,1 591.31,423.55Z"
android:fillColor="#6c63ff"/>
<path
android:pathData="M583.95,221.51s4.85,63.74 3.46,66.51 0,99.07 0,99.07 -9.7,51.96 2.77,54.73 9.7,-58.19 9.7,-58.19l12.47,-80.36 -1.39,-79.67Z"
android:fillColor="#ffb9b9"/>
<path
android:pathData="M594.34,181.32l-5.54,2.08s-10.39,43.65 -7.62,43.65 36.03,4.16 36.03,2.77 -6.24,-35.33 -6.24,-35.33Z"
android:fillColor="#6c63ff"/>
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_on_primary_surface_divider" />
<size android:height="1dp"
android:width="200dp"/>
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list android:shape="oval" xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="@color/keypass_white_50"/>
</shape>
</item>
<item android:drawable="@drawable/ic_baseline_casino_24"
android:left="@dimen/grid_1"
android:right="@dimen/grid_1"
android:top="@dimen/grid_1"
android:bottom="@dimen/grid_1"/>
</layer-list>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Work Sans"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Work Sans&amp;weight=500"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="mViewModel"
type="com.yogeshpaliyal.keypasscompose.ui.addTOTP.AddTOTPViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.addTOTP.AddTOTPActivity">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/toolbar"
app:navigationIcon="@drawable/ic_baseline_arrow_back_ios_24"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:endIconDrawable="@drawable/ic_twotone_qr_code_scanner_24"
app:endIconMode="custom"
android:id="@+id/tilSecretKey"
android:layout_marginTop="@dimen/grid_3"
android:layout_marginHorizontal="@dimen/grid_2">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Secret Key"
android:text="@={mViewModel.secretKey}"
android:id="@+id/etSecretKey"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tilSecretKey"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:endIconMode="custom"
android:id="@+id/tilAccountName"
android:layout_marginTop="@dimen/grid_2"
android:layout_marginHorizontal="@dimen/grid_2">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/account_name"
android:text="@={mViewModel.accountName}"
android:id="@+id/etAccount"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_margin="@dimen/grid_2"
android:text="@string/save"
android:id="@+id/btnSave"
app:icon="@drawable/ic_round_done_24"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.auth.AuthenticationActivity">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/btnRetry"
app:srcCompat="@drawable/ic_undraw_unlock_24mb"
android:adjustViewBounds="true"
android:id="@+id/illustrator"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/illustrator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/btnRetry"
android:text="Enter to KeyPass"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.CrashActivity">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/btnSendFeedback"
android:layout_margin="@dimen/grid_4"
app:layout_constrainedWidth="true"
android:id="@+id/txtCrash"
app:layout_constrainedHeight="true"/>
<com.google.android.material.button.MaterialButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnSendFeedback"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/send_feedback"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.yogeshpaliyal.keypasscompose.ui.home.DashboardViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context=".ui.nav.DashboardActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/searchAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/grid_2"
android:layout_marginVertical="@dimen/grid_1"
app:boxBackgroundColor="?attr/colorSurface"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="@dimen/grid_1"
app:boxCornerRadiusBottomStart="@dimen/grid_1"
app:boxCornerRadiusTopEnd="@dimen/grid_1"
app:boxCornerRadiusTopStart="@dimen/grid_1"
app:hintAnimationEnabled="false"
app:hintEnabled="false"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search"
android:paddingTop="@dimen/grid_2"
android:paddingBottom="@dimen/grid_2"
android:text="@={viewModel.keyword}" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchAppBar">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/bottom_nav_drawer"
android:name="com.yogeshpaliyal.keypasscompose.ui.nav.BottomNavDrawerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabCradleMargin="@dimen/bottom_app_bar_fab_cradle_margin"
app:menuAlignmentMode="start"
app:fabCradleRoundedCornerRadius="@dimen/bottom_app_bar_fab_cradle_corner_radius"
app:hideOnScroll="true"
app:menu="@menu/bottom_app_bar"
app:navigationIcon="@drawable/ic_round_menu_24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/bottomAppBar"
app:fabAnchorMode="embed"
app:srcCompat="@drawable/asl_add_save"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.generate.GeneratePasswordActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/grid_0_5"
android:paddingVertical="@dimen/grid_0_5">
<com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="@dimen/grid_1"
android:paddingVertical="@dimen/grid_1">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
app:endIconDrawable="@drawable/ic_twotone_content_copy_24"
app:endIconMode="custom"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toTopOf="@id/topGuideline">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_marginTop="@dimen/grid_2"
android:layout_height="wrap_content"
android:text="@string/password_length"/>
<com.google.android.material.slider.Slider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:valueFrom="7"
android:valueTo="50"
android:stepSize="1"
android:id="@+id/sliderPasswordLength"/>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/cbCapAlphabets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/uppercase_alphabets" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/cbLowerAlphabets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/lowercase_alphabets" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/cbNumbers"
android:layout_width="match_parent"
android:checked="true"
android:layout_height="wrap_content"
android:text="@string/numbers" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/cbSymbols"
android:layout_width="match_parent"
android:checked="true"
android:layout_height="wrap_content"
android:text="@string/symbols" />
</LinearLayout>
</com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/grid_3"
app:fabSize="normal"
android:src="@drawable/ic_baseline_refresh_24"
android:id="@+id/btnRefresh"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.addTOTP.ScannerActivity">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/toolbar"
app:navigationIcon="@drawable/ic_baseline_arrow_back_ios_24"
app:title="@string/scanner"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,25 @@
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_baseline_arrow_back_ios_24"
app:title="@string/credentials_backups" />
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/scrim_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:alpha="0"
android:background="?attr/scrimBackground"/>
<!--Container to act as the drawer's foreground container and hold all navigation-->
<FrameLayout
app:behavior_hideable="true"
app:behavior_skipCollapsed="true"
app:behavior_halfExpandedRatio="0.6"
android:id="@+id/foreground_container"
android:layout_width="match_parent"
app:layout_behavior="@string/bottom_sheet_behavior"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/grid_4">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/nav_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/grid_3"
android:paddingBottom="@dimen/bottom_app_bar_height"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,217 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="accountData"
type="com.yogeshpaliyal.common.data.AccountModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingHorizontal="@dimen/grid_0_5"
android:paddingVertical="@dimen/grid_0_5"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilAccountName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/account_name"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toTopOf="@id/topGuideline">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etAccountName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|text"
android:singleLine="true"
android:text="@={accountData.title}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_1"
android:hint="@string/username_email_phone"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toBottomOf="@id/tilAccountName">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|text"
android:singleLine="true"
android:text="@={accountData.username}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_1"
android:hint="@string/password"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toBottomOf="@id/tilUsername"
app:passwordToggleEnabled="true"
app:startIconDrawable="@drawable/ic_round_refresh_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|textPassword"
android:singleLine="true"
android:text="@={accountData.password}" />
<Button
android:id="@+id/btnScan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAllCaps="true"
app:icon="@drawable/ic_twotone_qr_code_scanner_24"
android:text="@string/scan_password" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilTags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_1"
android:hint="@string/tags_comma_separated_optional"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toBottomOf="@id/tilPassword">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etTag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|text"
android:singleLine="true"
android:text="@={accountData.tags}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilWebsite"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_1"
android:hint="@string/website_url_optional"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toBottomOf="@id/tilTags">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etWebsite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|textWebEditText"
android:singleLine="true"
android:text="@={accountData.site}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilNotes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/grid_1"
android:hint="@string/notes_optional"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
app:layout_constraintStart_toStartOf="@id/startGuideline"
app:layout_constraintTop_toBottomOf="@id/tilWebsite">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textNoSuggestions|text"
android:lines="3"
android:minLines="3"
android:text="@={accountData.notes}" />
</com.google.android.material.textfield.TextInputLayout>
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/grid_1"
app:layout_constraintTop_toBottomOf="@id/tilNotes" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/startGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="@dimen/grid_1" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/endGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="@dimen/grid_1" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="@dimen/grid_1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:fabAlignmentMode="end"
app:fabCradleMargin="@dimen/bottom_app_bar_fab_cradle_margin"
app:fabCradleRoundedCornerRadius="@dimen/bottom_app_bar_fab_cradle_corner_radius"
app:hideOnScroll="true"
app:navigationIcon="@drawable/ic_baseline_arrow_back_ios_24"
app:navigationIconTint="?attr/colorOnSurface" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/grid_2"
app:layout_anchor="@id/bottomAppBar"
app:srcCompat="@drawable/ic_round_done_24" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,20 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="@dimen/grid_0_25"
tools:listitem="@layout/item_accounts"
android:paddingBottom="@dimen/bottom_app_bar_height"
android:transitionGroup="true"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:paddingTopSystemWindowInsets="@{true}"
app:paddingBottomSystemWindowInsets="@{true}"/>
</layout>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.yogeshpaliyal.keypasscompose.data.MyAccountModel" />
<variable
name="model"
type="MyAccountModel" />
<variable
name="listener"
type="com.yogeshpaliyal.keypasscompose.listener.AccountsClickListener&lt;MyAccountModel>" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:cardUseCompatPadding="true"
android:elevation="@dimen/plane_00"
android:clickable="true"
android:focusable="true"
app:cardElevation="1dp"
app:strokeWidth="0dp"
app:layout_constraintBottom_toBottomOf="parent"
android:onClick="@{(view)->listener.onItemClick(view, model)}"
android:layout_marginVertical="@dimen/grid_0_25"
android:layout_marginHorizontal="@dimen/grid_0_5">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="60dp"
android:background="@drawable/avatar_none"
android:layout_height="60dp"
android:text="@{model.initials}"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/startIcon"/>
<TextView
android:id="@+id/txtTitle"
style="@style/TextStyle.Heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:text="@{model.title}"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/txtUsername"
app:layout_constraintEnd_toStartOf="@id/icnCopy"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/startIcon"
app:layout_constraintTop_toTopOf="@id/startIcon"
tools:text="@tools:sample/full_names" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
app:layout_constraintStart_toStartOf="@id/txtTitle"
app:layout_constraintEnd_toStartOf="@id/icnCopy"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/TextStyle.SubHeading"
android:text="@{model.username}"
android:id="@+id/txtUsername"
app:layout_constraintHorizontal_bias="0"
tools:text="@tools:sample/full_names"
app:layout_constrainedWidth="true"/>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_twotone_content_copy_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="@dimen/grid_1"
android:onClick="@{()->listener.onCopyClicked(model)}"
android:id="@+id/icnCopy"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.yogeshpaliyal.keypasscompose.data.MyAccountModel" />
<variable
name="model"
type="MyAccountModel" />
<variable
name="listener"
type="com.yogeshpaliyal.keypasscompose.listener.AccountsClickListener&lt;MyAccountModel>" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:cardUseCompatPadding="true"
android:elevation="@dimen/plane_00"
app:cardElevation="1dp"
app:strokeWidth="0dp"
android:clickable="true"
android:focusable="true"
android:onClick="@{(view)->listener.onItemClick(view, model)}"
android:layout_marginVertical="@dimen/grid_0_25"
android:layout_marginHorizontal="@dimen/grid_0_5"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingVertical="12dp"
android:paddingEnd="12dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="60dp"
android:background="@drawable/avatar_none"
android:layout_height="60dp"
android:text="@{model.initials}"
android:gravity="center"
android:layout_marginStart="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/startIcon"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/startIcon"
app:layout_constraintBottom_toBottomOf="@id/startIcon"
app:layout_constraintEnd_toEndOf="@id/startIcon"
app:layout_constraintStart_toStartOf="@id/startIcon"
android:progress="@{model.getTOtpProgress()}"
app:indicatorSize="65dp"
android:padding="0dp"
android:minHeight="0dp"
android:maxHeight="0dp"
tools:progress="100"
android:max="30"/>
<TextView
android:id="@+id/txtTitle"
style="@style/TextStyle.Heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:text="@{model.title}"
app:layout_constrainedWidth="true"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/txtUsername"
app:layout_constraintEnd_toStartOf="@id/icnCopy"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/startIcon"
app:layout_constraintTop_toTopOf="@id/startIcon"
tools:text="@tools:sample/full_names" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
app:layout_constraintStart_toStartOf="@id/txtTitle"
app:layout_constraintEnd_toStartOf="@id/icnCopy"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/TextStyle.SubHeading"
android:text="@{model.getOtp()}"
android:id="@+id/txtUsername"
android:textSize="22sp"
app:layout_constraintHorizontal_bias="0"
tools:text="123359"
app:layout_constrainedWidth="true"/>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_twotone_content_copy_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:padding="@dimen/grid_1"
android:onClick="@{()->listener.onCopyClicked(model)}"
android:id="@+id/icnCopy"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.yogeshpaliyal.keypasscompose.custom_views.MaskedCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_marginTop="@dimen/grid_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textSize="18sp"
android:textColor="@color/keypass_blue_600"
android:id="@+id/txtTitle"
android:text="@string/alert"/>
<TextView
android:layout_marginTop="@dimen/grid_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="@dimen/grid_3"
app:layout_constrainedWidth="true"
android:gravity="center"
android:textColor="@color/keypass_blue_300"
android:textSize="14sp"
android:id="@+id/txtMessage"
android:text="@string/copy_keypharse_msg"/>
<TextView
android:layout_marginTop="@dimen/grid_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/txtCode"
app:layout_constraintTop_toBottomOf="@id/txtMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textSize="18sp"
android:padding="@dimen/grid_1"
android:textColor="@color/keypass_blue_600"
tools:text="1234567890123456"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="@dimen/grid_2"
android:textSize="18sp"
android:textColor="@color/keypass_blue_600"
android:id="@+id/txtTitle"
android:text="@string/set_keyphrase"/>
<TextView
android:layout_marginTop="@dimen/grid_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="@dimen/grid_3"
app:layout_constrainedWidth="true"
android:gravity="center"
android:textColor="@color/keypass_blue_300"
android:textSize="14sp"
android:id="@+id/txtMessage"
android:text="@string/set_keyphrase_info"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/tilKeyPhrase"
app:boxBackgroundMode="outline"
android:backgroundTint="@android:color/transparent"
app:boxBackgroundColor="@android:color/transparent"
android:layout_marginTop="@dimen/grid_2"
android:hint="@string/enter_keyphrase"
android:layout_marginHorizontal="@dimen/grid_3"
app:counterEnabled="true"
app:counterMaxLength="16">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="1234"
android:id="@+id/etKeyPhrase"
android:textColor="@color/keypass_blue_600"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="message"
type="String" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/illustrator"
app:layout_constraintVertical_bias="0.7"
android:gravity="center"
android:text="@string/message_no_accounts"
app:layout_constrainedWidth="true"
style="@style/TextStyle.Heading"
android:layout_marginHorizontal="@dimen/grid_4"/>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:id="@+id/illustrator"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_undraw_empty_street_sfxm"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="@dimen/grid_2"
android:textSize="18sp"
android:textColor="@color/keypass_blue_600"
android:id="@+id/txtTitle"
android:text="@string/restore"/>
<TextView
android:layout_marginTop="@dimen/grid_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="@dimen/grid_3"
app:layout_constrainedWidth="true"
android:gravity="center"
android:textColor="@color/keypass_blue_300"
android:textSize="14sp"
android:id="@+id/txtMessage"
android:text="@string/keyphrase_restore_info"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/txtMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/tilKeyPhrase"
app:boxBackgroundMode="outline"
android:backgroundTint="@android:color/transparent"
app:boxBackgroundColor="@android:color/transparent"
android:layout_marginTop="@dimen/grid_2"
android:hint="@string/enter_keyphrase"
android:layout_marginHorizontal="@dimen/grid_3">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="1234"
android:id="@+id/etKeyPhrase"
android:textColor="@color/keypass_blue_600"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?><!--
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
android:src="@drawable/ic_baseline_feedback_24" />
</LinearLayout>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="navDivider"
type="com.yogeshpaliyal.keypasscompose.ui.nav.NavigationModelItem.NavDivider" />
</data>
<TextView
android:id="@+id/divider_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/grid_2"
android:layout_marginHorizontal="@dimen/grid_4"
android:text="@{navDivider.title}"
android:textAppearance="?attr/textAppearanceOverline"
android:textColor="?attr/colorOnSurface"
android:drawablePadding="@dimen/grid_4"
app:drawableTopCompat="@drawable/nav_divider_top" />
</layout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.yogeshpaliyal.keypasscompose.ui.nav.NavigationModelItem.NavEmailFolder"/>
<variable
name="navEmailFolder"
type="NavEmailFolder" />
<variable
name="navListener"
type="com.yogeshpaliyal.keypasscompose.ui.nav.NavigationAdapter.NavigationAdapterListener" />
</data>
<TextView
android:id="@+id/nav_item_title"
android:layout_width="match_parent"
android:layout_height="@dimen/navigation_drawer_menu_item_height"
android:paddingLeft="@dimen/grid_4"
android:paddingRight="@dimen/grid_4"
android:gravity="center_vertical|start"
android:onClick="@{() -> navListener.onNavEmailFolderClicked(navEmailFolder)}"
android:ellipsize="end"
android:lines="1"
android:text="@{navEmailFolder.category}"
android:textColor="?attr/colorOnSurface"
android:textAppearance="?attr/textAppearanceSubtitle1"
app:drawableStartCompat="@drawable/ic_twotone_folder"
app:drawableTint="?attr/colorOnSurface"
android:drawablePadding="@dimen/grid_4"
tools:text="Pine Elementary" />
</layout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.yogeshpaliyal.keypasscompose.ui.nav.NavigationModelItem.NavMenuItem"/>
<variable
name="navMenuItem"
type="NavMenuItem" />
<variable
name="navListener"
type="com.yogeshpaliyal.keypasscompose.ui.nav.NavigationAdapter.NavigationAdapterListener" />
</data>
<CheckedTextView
android:id="@+id/nav_item_title"
android:layout_width="match_parent"
android:layout_height="@dimen/navigation_drawer_menu_item_height"
android:paddingLeft="@dimen/grid_4"
android:paddingRight="@dimen/grid_4"
android:gravity="center_vertical|start"
android:onClick="@{() -> navListener.onNavMenuItemClicked(navMenuItem)}"
android:ellipsize="end"
android:lines="1"
android:text="@{navMenuItem.titleRes}"
android:checked="@{navMenuItem.checked}"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textColor="@color/color_navigation_drawer_menu_item"
app:drawableTint="@color/color_navigation_drawer_menu_item"
app:drawableStart="@{navMenuItem.icon}"
android:drawablePadding="@dimen/grid_4"
tools:text="Inbox" />
</layout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_settings"
android:title="Settings"/>
</menu>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_delete"
android:icon="@drawable/ic_twotone_delete_24"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_settings"
android:title="Settings"
app:showAsAction="always"
android:icon="@drawable/ic_baseline_settings_24"/>
</menu>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_delete"
android:icon="@drawable/ic_twotone_delete_24"
android:title="@string/delete"/>
</menu>

Some files were not shown because too many files have changed in this diff Show More