mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-07 09:00:04 -06:00
Working on backup and restore
This commit is contained in:
@@ -3,6 +3,7 @@ package com.yogeshpaliyal.keypass
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.yogeshpaliyal.keypass.data.AccountModel
|
||||
import com.yogeshpaliyal.keypass.db.DbDao
|
||||
@@ -31,8 +32,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
if (!this::_instance.isInitialized)
|
||||
|
||||
|
||||
|
||||
|
||||
synchronized(this) {
|
||||
|
||||
|
||||
@@ -43,18 +42,25 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
).addCallback(object : Callback() {
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
super.onCreate(db)
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
|
||||
super.onDestructiveMigration(db)
|
||||
onCreate(db)
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
.fallbackToDestructiveMigration()
|
||||
/* .addMigrations(object : Migration(3, 4) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
val cursor = database.query("SELECT * FROM account")
|
||||
while(cursor.moveToNext()) {
|
||||
val id = cursor.getLong(cursor.getColumnIndex("id"))
|
||||
val password = cursor.getLong(cursor.getColumnIndex("password"))
|
||||
//-- Hash your password --//
|
||||
database.execSQL("UPDATE account SET password = hashedPassword WHERE id = $id;")
|
||||
}
|
||||
}
|
||||
})*/
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -62,4 +68,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
return _instance
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import com.yogeshpaliyal.universal_adapter.model.BaseDiffUtil
|
||||
class AccountModel(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id")
|
||||
val id: Long? = null,
|
||||
var id: Long? = null,
|
||||
@ColumnInfo(name = "title")
|
||||
var title: String? = null,
|
||||
@ColumnInfo(name = "username")
|
||||
|
||||
@@ -21,6 +21,9 @@ abstract class DbDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertOrUpdateAccount(accountModel: AccountModel)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertOrUpdateAccount(accountModel: List<AccountModel>)
|
||||
|
||||
|
||||
@Query("SELECT * from account")
|
||||
abstract fun getAllAccounts() : Flow<List<AccountModel>>
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.yogeshpaliyal.keypass.db_helper
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import androidx.room.withTransaction
|
||||
import com.google.gson.Gson
|
||||
import com.yogeshpaliyal.keypass.AppDatabase
|
||||
import com.yogeshpaliyal.keypass.data.AccountModel
|
||||
@@ -12,6 +15,8 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
|
||||
/*
|
||||
@@ -36,7 +41,14 @@ suspend fun AppDatabase.createBackup(contentResolver : ContentResolver,fileUri:
|
||||
suspend fun AppDatabase.restoreBackup(contentResolver : ContentResolver,fileUri: Uri?) = withContext(Dispatchers.IO){
|
||||
fileUri ?: return@withContext false
|
||||
|
||||
EncryptionHelper.doCryptoDecrypt(getOrCreateBackupKey(), contentResolver.openInputStream(fileUri)).logD("DecryptedFile")
|
||||
|
||||
val restoredFile = EncryptionHelper.doCryptoDecrypt(getOrCreateBackupKey(), contentResolver.openInputStream(fileUri))
|
||||
val data = Gson().fromJson(restoredFile, Array<AccountModel>::class.java).toList()
|
||||
data.forEach {
|
||||
it.id = null
|
||||
}
|
||||
withTransaction {
|
||||
getDao().insertOrUpdateAccount(data.toList())
|
||||
}
|
||||
return@withContext true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.yogeshpaliyal.keypass.db_helper
|
||||
|
||||
|
||||
import java.io.*
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.Key
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.InvalidParameterSpecException
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
@@ -20,20 +24,28 @@ import javax.crypto.spec.SecretKeySpec
|
||||
*/
|
||||
object EncryptionHelper {
|
||||
private const val ALGORITHM = "AES"
|
||||
private const val TRANSFORMATION = "AES/GCM/NoPadding"
|
||||
// private const val TRANSFORMATION = "AES/GCM/PKCS5Padding"
|
||||
private const val TRANSFORMATION = "AES"
|
||||
// private const val TRANSFORMATION = "DES/CBC/PKCS5Padding"
|
||||
|
||||
|
||||
private const val TAG_LENGTH_BIT = 128
|
||||
private const val IV_LENGTH_BYTE = 12
|
||||
|
||||
|
||||
@Throws(CryptoException::class)
|
||||
fun doCryptoEncrypt(key: String, data: String,
|
||||
fun doCryptoEncrypt(
|
||||
key: String, data: String,
|
||||
outputFile: OutputStream?
|
||||
) {
|
||||
try {
|
||||
val iv = ByteArray(IV_LENGTH_BYTE)
|
||||
|
||||
val secretKey: Key =
|
||||
SecretKeySpec(key.toByteArray(), ALGORITHM)
|
||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
// val spec = GCMParameterSpec(TAG_LENGTH_BIT, iv)
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
|
||||
data.byteInputStream().use {
|
||||
@@ -76,6 +88,8 @@ object EncryptionHelper {
|
||||
val secretKey: Key =
|
||||
SecretKeySpec(key.toByteArray(), ALGORITHM)
|
||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey)
|
||||
|
||||
inputFile.use {
|
||||
@@ -112,4 +126,37 @@ object EncryptionHelper {
|
||||
|
||||
|
||||
|
||||
fun encryptPassword(message: String, password: String): String{
|
||||
/* Encrypt the message. */
|
||||
var cipher: Cipher? = null
|
||||
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, generateKey(password))
|
||||
return String(cipher.doFinal(message.encodeToByteArray()))
|
||||
}
|
||||
|
||||
@Throws(
|
||||
NoSuchPaddingException::class,
|
||||
NoSuchAlgorithmException::class,
|
||||
InvalidParameterSpecException::class,
|
||||
InvalidAlgorithmParameterException::class,
|
||||
InvalidKeyException::class,
|
||||
BadPaddingException::class,
|
||||
IllegalBlockSizeException::class,
|
||||
UnsupportedEncodingException::class
|
||||
)
|
||||
fun decryptMsg(encryptedMessage: String, password: String): String {
|
||||
/* Decrypt the message, given derived encContentValues and initialization vector. */
|
||||
var cipher: Cipher? = null
|
||||
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, generateKey(password))
|
||||
return String(cipher.doFinal(encryptedMessage.toByteArray()))
|
||||
}
|
||||
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
|
||||
fun generateKey(passLockKey: String): SecretKey {
|
||||
return SecretKeySpec(passLockKey.encodeToByteArray(), "AES")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.yogeshpaliyal.keypass.ui.auth
|
||||
|
||||
import android.R.attr.description
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
@@ -11,7 +9,6 @@ import androidx.core.content.ContextCompat
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.databinding.ActivityAuthenticationBinding
|
||||
import com.yogeshpaliyal.keypass.ui.nav.DashboardActivity
|
||||
import com.yogeshpaliyal.keypass.utils.SharedPrefHelper
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
|
||||
@@ -23,9 +20,6 @@ class AuthenticationActivity : AppCompatActivity() {
|
||||
private lateinit var biometricPrompt: BiometricPrompt
|
||||
private lateinit var promptInfo: BiometricPrompt.PromptInfo
|
||||
|
||||
private val userPin by lazy {
|
||||
SharedPrefHelper.getString(SharedPrefHelper.SharedPrefKeys.USER_PIN)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -78,7 +72,6 @@ class AuthenticationActivity : AppCompatActivity() {
|
||||
// Consider integrating with the keystore to unlock cryptographic operations,
|
||||
// if needed by your app.
|
||||
|
||||
|
||||
biometricPrompt.authenticate(promptInfo)
|
||||
|
||||
binding.btnRetry.setOnClickListener {
|
||||
|
||||
@@ -11,9 +11,11 @@ import androidx.navigation.fragment.navArgs
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.data.AccountModel
|
||||
import com.yogeshpaliyal.keypass.databinding.FragmentHomeBinding
|
||||
import com.yogeshpaliyal.keypass.db_helper.EncryptionHelper
|
||||
import com.yogeshpaliyal.keypass.listener.UniversalClickListener
|
||||
import com.yogeshpaliyal.keypass.ui.detail.DetailActivity
|
||||
import com.yogeshpaliyal.keypass.utils.initViewModel
|
||||
import com.yogeshpaliyal.keypass.utils.logD
|
||||
import com.yogeshpaliyal.universal_adapter.adapter.UniversalAdapterViewType
|
||||
import com.yogeshpaliyal.universal_adapter.adapter.UniversalRecyclerAdapter
|
||||
import com.yogeshpaliyal.universal_adapter.utils.Resource
|
||||
|
||||
@@ -2,18 +2,22 @@ package com.yogeshpaliyal.keypass.ui.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.DocumentsContract
|
||||
import android.widget.Toast
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.yogeshpaliyal.keypass.AppDatabase
|
||||
import com.yogeshpaliyal.keypass.BuildConfig
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.db_helper.createBackup
|
||||
import com.yogeshpaliyal.keypass.db_helper.restoreBackup
|
||||
import com.yogeshpaliyal.keypass.utils.canUserAccessBackupDirectory
|
||||
import com.yogeshpaliyal.keypass.utils.email
|
||||
import com.yogeshpaliyal.keypass.utils.getBackupDirectory
|
||||
import com.yogeshpaliyal.keypass.utils.setBackupDirectory
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
@@ -40,31 +44,45 @@ class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
selectRestoreFile()
|
||||
return 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, "Share KeyPass"))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
|
||||
private fun selectBackupDirectory(){
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
val selectedDirectory = Uri.parse(getBackupDirectory())
|
||||
|
||||
/*if (Build.VERSION.SDK_INT >= 26) {
|
||||
intent.putExtra(
|
||||
DocumentsContract.EXTRA_INITIAL_URI,
|
||||
SignalStore.settings().getLatestSignalBackupDirectory()
|
||||
)
|
||||
}*/
|
||||
context?.let {
|
||||
if(it.canUserAccessBackupDirectory()){
|
||||
backup(selectedDirectory)
|
||||
}else{
|
||||
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
|
||||
)
|
||||
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()
|
||||
try {
|
||||
startActivityForResult(intent, CHOOSE_BACKUPS_LOCATION_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -73,15 +91,8 @@ class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
|
||||
/*if (Build.VERSION.SDK_INT >= 26) {
|
||||
intent.putExtra(
|
||||
DocumentsContract.EXTRA_INITIAL_URI,
|
||||
SignalStore.settings().getLatestSignalBackupDirectory()
|
||||
)
|
||||
}*/
|
||||
|
||||
intent.addFlags(
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
@@ -103,12 +114,8 @@ class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
|
||||
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile("*/*","key_pass_backup_${System.currentTimeMillis()}.keypass")
|
||||
|
||||
lifecycleScope.launch {
|
||||
AppDatabase.getInstance().createBackup(contentResolver, tempFile?.uri)
|
||||
Toast.makeText(context, "File saved", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
setBackupDirectory(selectedDirectory.toString())
|
||||
backup(selectedDirectory)
|
||||
}
|
||||
} else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK){
|
||||
val contentResolver = context?.contentResolver
|
||||
@@ -120,10 +127,30 @@ class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
AppDatabase.getInstance().restoreBackup(contentResolver, selectedFile)
|
||||
Toast.makeText(context, "File saved", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.backup_restored), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun backup(selectedDirectory: Uri){
|
||||
|
||||
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
|
||||
"*/*",
|
||||
"key_pass_backup_${System.currentTimeMillis()}.keypass"
|
||||
)
|
||||
|
||||
lifecycleScope.launch {
|
||||
context?.contentResolver?.let { AppDatabase.getInstance().createBackup(
|
||||
it,
|
||||
tempFile?.uri
|
||||
)
|
||||
Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.yogeshpaliyal.keypass.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
|
||||
/*
|
||||
@@ -12,7 +15,8 @@ import java.util.*
|
||||
*/
|
||||
|
||||
fun getRandomString(sizeOfRandomString: Int): String {
|
||||
val ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%&*_+"
|
||||
val ALLOWED_CHARACTERS =
|
||||
"0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM!@#$%&*_+"
|
||||
val random = SecureRandom()
|
||||
val sb = StringBuilder(sizeOfRandomString)
|
||||
for (i in 0 until sizeOfRandomString) sb.append(
|
||||
@@ -22,3 +26,20 @@ fun getRandomString(sizeOfRandomString: Int): String {
|
||||
)
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
|
||||
fun Context.canUserAccessBackupDirectory(): Boolean {
|
||||
val backupDirectoryUri = getUri(getBackupDirectory()) ?: return false
|
||||
val backupDirectory = DocumentFile.fromTreeUri(this, backupDirectoryUri)
|
||||
return backupDirectory != null && backupDirectory.exists() && backupDirectory.canRead() && backupDirectory.canWrite()
|
||||
}
|
||||
|
||||
|
||||
private fun getUri(string: String?): Uri? {
|
||||
val uri = string
|
||||
return if (TextUtils.isEmpty(uri)) {
|
||||
null
|
||||
} else {
|
||||
Uri.parse(uri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.yogeshpaliyal.keypass.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringDef
|
||||
import androidx.core.content.edit
|
||||
import com.yogeshpaliyal.keypass.MyApplication
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
|
||||
|
||||
/*
|
||||
* @author Yogesh Paliyal
|
||||
* techpaliyal@gmail.com
|
||||
* https://techpaliyal.com
|
||||
* created on 22-01-2021 22:40
|
||||
*/
|
||||
|
||||
object SharedPrefHelper {
|
||||
|
||||
@StringDef(open = false,value = [
|
||||
SharedPrefKeys.USER_PIN
|
||||
])
|
||||
annotation class SharedPrefKeys{
|
||||
companion object{
|
||||
const val USER_PIN = "USER_PIN"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private val sp by lazy {
|
||||
MyApplication.instance.getSharedPreferences(
|
||||
MyApplication.instance.getString(R.string.app_name),
|
||||
Context.MODE_PRIVATE
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fun setString(@SharedPrefKeys key: String,value : String?){
|
||||
sp.edit { putString(key,value)}
|
||||
}
|
||||
|
||||
fun getString(@SharedPrefKeys key: String, default : String = "") = sp.getString(key,default)
|
||||
|
||||
}
|
||||
@@ -59,4 +59,18 @@ fun getOrCreateBackupKey(): String{
|
||||
}
|
||||
}
|
||||
|
||||
private const val BACKUP_KEY = "backup_key"
|
||||
|
||||
fun setBackupDirectory(string: String){
|
||||
getSharedPreferences().edit {
|
||||
putString(BACKUP_DIRECTORY, string)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getBackupDirectory(): String{
|
||||
val sp = getSharedPreferences()
|
||||
return sp.getString(BACKUP_DIRECTORY,"") ?: ""
|
||||
}
|
||||
|
||||
private const val BACKUP_KEY = "backup_key"
|
||||
private const val BACKUP_DIRECTORY = "backup_directory"
|
||||
10
app/src/main/res/drawable/ic_baseline_backup_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_backup_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="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"/>
|
||||
</vector>
|
||||
@@ -18,6 +18,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/illustrator"
|
||||
app:layout_constraintVertical_bias="0.7"
|
||||
android:gravity="center"
|
||||
android:text="No Accounts added, please add from below button"
|
||||
app:layout_constrainedWidth="true"
|
||||
style="@style/TextStyle.Heading"
|
||||
|
||||
17
app/src/main/res/layout/layut_backup_keypharse.xml
Normal file
17
app/src/main/res/layout/layut_backup_keypharse.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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">
|
||||
|
||||
<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:textSize="14sp"
|
||||
|
||||
android:text="Copy This Key Phrase this will be used to recover this backup"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,6 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.KeyPass" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<style name="Theme.KeyPass" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!--Color-->
|
||||
<item name="colorPrimary">@color/keypass_blue_200</item>
|
||||
<item name="colorPrimaryVariant">@color/keypass_blue_300</item>
|
||||
|
||||
@@ -9,4 +9,8 @@
|
||||
|
||||
<string name="generate_shortcut_short_label">Generate Password</string>
|
||||
<string name="retry">Retry</string>
|
||||
|
||||
<string name="backup_restored">Backup Restored</string>
|
||||
|
||||
<string name="backup_completed">Backup Completed</string>
|
||||
</resources>
|
||||
@@ -8,13 +8,11 @@
|
||||
|
||||
<Preference
|
||||
app:key="backup"
|
||||
app:icon="@drawable/ic_baseline_feedback_24"
|
||||
app:title="Backup"
|
||||
app:summary="Backup your credentials in encrypted file" />
|
||||
|
||||
<Preference
|
||||
app:key="restore"
|
||||
app:icon="@drawable/ic_baseline_share_24"
|
||||
app:title="Restore"
|
||||
app:summary="Restore your backup" />
|
||||
</PreferenceCategory>
|
||||
@@ -34,5 +32,6 @@
|
||||
app:icon="@drawable/ic_baseline_share_24"
|
||||
app:title="Share"
|
||||
app:summary="Share app with others" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user