mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-04 01:29:39 -06:00
copied xml code to compose module (#305)
This commit is contained in:
committed by
GitHub
parent
b075fec701
commit
34613b29fa
@@ -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")
|
||||
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package com.yogeshpaliyal.keypasscompose.ui.theme
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
37
keypasscompose/src/main/res/drawable/asl_add_save.xml
Normal file
37
keypasscompose/src/main/res/drawable/asl_add_save.xml
Normal 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>
|
||||
5
keypasscompose/src/main/res/drawable/avatar_none.xml
Normal file
5
keypasscompose/src/main/res/drawable/avatar_none.xml
Normal 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>
|
||||
126
keypasscompose/src/main/res/drawable/avd_add_to_save.xml
Normal file
126
keypasscompose/src/main/res/drawable/avd_add_to_save.xml
Normal 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>
|
||||
127
keypasscompose/src/main/res/drawable/avd_save_to_add.xml
Normal file
127
keypasscompose/src/main/res/drawable/avd_save_to_add.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
10
keypasscompose/src/main/res/drawable/ic_round_add_24.xml
Normal file
10
keypasscompose/src/main/res/drawable/ic_round_add_24.xml
Normal 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>
|
||||
10
keypasscompose/src/main/res/drawable/ic_round_done_24.xml
Normal file
10
keypasscompose/src/main/res/drawable/ic_round_done_24.xml
Normal 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>
|
||||
10
keypasscompose/src/main/res/drawable/ic_round_menu_24.xml
Normal file
10
keypasscompose/src/main/res/drawable/ic_round_menu_24.xml
Normal 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>
|
||||
10
keypasscompose/src/main/res/drawable/ic_round_refresh_24.xml
Normal file
10
keypasscompose/src/main/res/drawable/ic_round_refresh_24.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
14
keypasscompose/src/main/res/drawable/ic_twotone_folder.xml
Normal file
14
keypasscompose/src/main/res/drawable/ic_twotone_folder.xml
Normal 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>
|
||||
15
keypasscompose/src/main/res/drawable/ic_twotone_home_24.xml
Normal file
15
keypasscompose/src/main/res/drawable/ic_twotone_home_24.xml
Normal 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>
|
||||
@@ -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>
|
||||
10
keypasscompose/src/main/res/drawable/ic_twotone_totp.xml
Normal file
10
keypasscompose/src/main/res/drawable/ic_twotone_totp.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
194
keypasscompose/src/main/res/drawable/ic_undraw_unlock_24mb.xml
Normal file
194
keypasscompose/src/main/res/drawable/ic_undraw_unlock_24mb.xml
Normal 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>
|
||||
6
keypasscompose/src/main/res/drawable/nav_divider_top.xml
Normal file
6
keypasscompose/src/main/res/drawable/nav_divider_top.xml
Normal 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>
|
||||
13
keypasscompose/src/main/res/drawable/white_circle.xml
Normal file
13
keypasscompose/src/main/res/drawable/white_circle.xml
Normal 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>
|
||||
7
keypasscompose/src/main/res/font/work_sans.xml
Normal file
7
keypasscompose/src/main/res/font/work_sans.xml
Normal 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>
|
||||
7
keypasscompose/src/main/res/font/work_sans_medium.xml
Normal file
7
keypasscompose/src/main/res/font/work_sans_medium.xml
Normal 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&weight=500"
|
||||
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
|
||||
</font-family>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
39
keypasscompose/src/main/res/layout/activity_crash.xml
Normal file
39
keypasscompose/src/main/res/layout/activity_crash.xml
Normal 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>
|
||||
104
keypasscompose/src/main/res/layout/activity_dashboard.xml
Normal file
104
keypasscompose/src/main/res/layout/activity_dashboard.xml
Normal 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>
|
||||
@@ -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>
|
||||
28
keypasscompose/src/main/res/layout/activity_scanner.xml
Normal file
28
keypasscompose/src/main/res/layout/activity_scanner.xml
Normal 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>
|
||||
25
keypasscompose/src/main/res/layout/backup_activity.xml
Normal file
25
keypasscompose/src/main/res/layout/backup_activity.xml
Normal 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>
|
||||
@@ -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>
|
||||
217
keypasscompose/src/main/res/layout/fragment_detail.xml
Normal file
217
keypasscompose/src/main/res/layout/fragment_detail.xml
Normal 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>
|
||||
20
keypasscompose/src/main/res/layout/fragment_home.xml
Normal file
20
keypasscompose/src/main/res/layout/fragment_home.xml
Normal 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>
|
||||
96
keypasscompose/src/main/res/layout/item_accounts.xml
Normal file
96
keypasscompose/src/main/res/layout/item_accounts.xml
Normal 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<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>
|
||||
118
keypasscompose/src/main/res/layout/item_totp.xml
Normal file
118
keypasscompose/src/main/res/layout/item_totp.xml
Normal 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<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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
38
keypasscompose/src/main/res/layout/layout_no_accounts.xml
Normal file
38
keypasscompose/src/main/res/layout/layout_no_accounts.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
48
keypasscompose/src/main/res/layout/nav_menu_item_layout.xml
Normal file
48
keypasscompose/src/main/res/layout/nav_menu_item_layout.xml
Normal 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>
|
||||
5
keypasscompose/src/main/res/menu/bottom_app_bar.xml
Normal file
5
keypasscompose/src/main/res/menu/bottom_app_bar.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
6
keypasscompose/src/main/res/menu/menu_delete.xml
Normal file
6
keypasscompose/src/main/res/menu/menu_delete.xml
Normal 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
Reference in New Issue
Block a user