Merge pull request #23 from yogeshpaliyal/feature/auto_backup

Upgrade to AGP 7, Dependencies updated
This commit is contained in:
Yogesh Choudhary Paliyal
2021-08-31 20:28:38 +05:30
committed by GitHub
57 changed files with 353 additions and 414 deletions

View File

@@ -3,7 +3,7 @@
![GitHub release (latest by date)](https://img.shields.io/github/v/release/yogeshpaliyal/KeyPass?style=for-the-badge)
![Cover Image](https://github.com/yogeshpaliyal/KeyPass/blob/master/images/KeyPass%20Cover.jpg)
![Cover Image](https://github.com/yogeshpaliyal/KeyPass/raw/master/images/KeyPass%20Cover.jpg)

4
app/.gitignore vendored
View File

@@ -1 +1,3 @@
/build
/build
**.aab
**.apk

View File

@@ -1,7 +0,0 @@
[packages]
com.yogeshpaliyal.keypass = raa
[localazy]
allStringsUploaded = true
showWelcomeMessage = false

View File

@@ -62,7 +62,7 @@ android {
}
lintOptions{
abortOnError false
abortOnError true
}
testOptions.unitTests.includeAndroidResources = true
@@ -79,6 +79,7 @@ dependencies {
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

Binary file not shown.

View File

@@ -0,0 +1,18 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.yogeshpaliyal.keypass",
"variantName": "processProductionReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 3,
"versionName": "1.3",
"outputFile": "app-production-release.apk"
}
]
}

View File

@@ -1,13 +1,11 @@
package com.yogeshpaliyal.keypass
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yogeshpaliyal.keypass", appContext.packageName)
}
}
}

View File

@@ -1,14 +1,9 @@
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
import com.yogeshpaliyal.keypass.utils.getRandomString
/*
* @author Yogesh Paliyal
@@ -25,4 +20,4 @@ abstract class AppDatabase : RoomDatabase() {
// define DAO start
abstract fun getDao(): DbDao
// define DAO end
}
}

View File

@@ -1,15 +1,16 @@
package com.yogeshpaliyal.keypass
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 23-01-2021 10:33
*/
data class InfoModel(var user_id: String,
var username : String,
var password : String,
var notes: String,
var insertedDate : Long,
var lastUpdatedDate : Long)
data class InfoModel(
var user_id: String,
var username: String,
var password: String,
var notes: String,
var insertedDate: Long,
var lastUpdatedDate: Long
)

View File

@@ -3,7 +3,6 @@ package com.yogeshpaliyal.keypass
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
/*
* @author Yogesh Paliyal
* yogeshpaliyal.foss@gmail.com
@@ -13,14 +12,12 @@ import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application() {
companion object{
companion object {
lateinit var instance: MyApplication
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
}

View File

@@ -44,4 +44,4 @@ class MaskedCardView @JvmOverloads constructor(
pathProvider.calculatePath(shapeAppearance, 1f, rectF, path)
super.onSizeChanged(w, h, oldw, oldh)
}
}
}

View File

@@ -6,7 +6,6 @@ import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import com.yogeshpaliyal.universal_adapter.model.BaseDiffUtil
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -49,8 +48,10 @@ data class AccountModel(
var tags: String? = null
) : BaseDiffUtil {
fun getInitials() = (title?.firstOrNull() ?: username?.firstOrNull() ?: site?.firstOrNull()
?: notes?.firstOrNull() ?: 'K').toString()
fun getInitials() = (
title?.firstOrNull() ?: username?.firstOrNull() ?: site?.firstOrNull()
?: notes?.firstOrNull() ?: 'K'
).toString()
override fun getDiffId(): Any? {
return id

View File

@@ -3,7 +3,6 @@ package com.yogeshpaliyal.keypass.data
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -13,8 +12,8 @@ import com.google.gson.annotations.SerializedName
data class BackupData(
@SerializedName("version")
@Expose
val version : Int,
val version: Int,
@SerializedName("data")
@Expose
val data : List<AccountModel> ) {
}
val data: List<AccountModel>
)

View File

@@ -8,7 +8,6 @@ import androidx.room.Query
import com.yogeshpaliyal.keypass.data.AccountModel
import kotlinx.coroutines.flow.Flow
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -25,7 +24,6 @@ abstract class DbDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertOrUpdateAccount(accountModel: List<AccountModel>)
@Query("SELECT * FROM account ORDER BY title ASC")
abstract fun getAllAccounts(): LiveData<List<AccountModel>>
@@ -43,6 +41,4 @@ abstract class DbDao {
@Query("DELETE from account WHERE id = :id")
abstract fun deleteAccount(id: Long?)
}
}

View File

@@ -1,6 +1,5 @@
package com.yogeshpaliyal.keypass.db_helper
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com

View File

@@ -11,7 +11,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -32,7 +31,6 @@ suspend fun AppDatabase.createBackup(key: String, contentResolver: ContentResolv
return@withContext true
}
suspend fun AppDatabase.restoreBackup(
key: String,
contentResolver: ContentResolver,
@@ -47,7 +45,7 @@ suspend fun AppDatabase.restoreBackup(
return@withContext false
}
return@withContext Gson().fromJson(restoredFile, BackupData::class.java)?.let{ data ->
return@withContext Gson().fromJson(restoredFile, BackupData::class.java)?.let { data ->
if (data.version == 3) {
for (datum in data.data) {
datum.uniqueId = getRandomString()
@@ -59,8 +57,6 @@ suspend fun AppDatabase.restoreBackup(
withTransaction {
getDao().insertOrUpdateAccount(data.data)
}
true
true
} ?: false
}

View File

@@ -1,6 +1,5 @@
package com.yogeshpaliyal.keypass.db_helper
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@@ -15,7 +14,6 @@ import javax.crypto.*
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -25,21 +23,20 @@ import javax.crypto.spec.SecretKeySpec
*/
object EncryptionHelper {
private const val ALGORITHM = "AES"
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
//private const val TRANSFORMATION = "AES"
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
// private const val TRANSFORMATION = "AES"
// private const val TRANSFORMATION = "DES/CBC/PKCS5Padding"
private val iV = byteArrayOf(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
private val iV = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
@Throws(CryptoException::class)
fun doCryptoEncrypt(
key: String, data: String,
key: String,
data: String,
outputFile: OutputStream?
) {
try {
val secretKey: Key =
SecretKeySpec(key.toByteArray(), ALGORITHM)
val cipher = Cipher.getInstance(TRANSFORMATION)
@@ -47,7 +44,7 @@ object EncryptionHelper {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iV))
data.byteInputStream().use {
val inputStream = it
val inputStream = it
outputFile?.use {
val outputStream = it
CipherOutputStream(outputStream, cipher).use {
@@ -62,7 +59,7 @@ object EncryptionHelper {
// Log.d("TestingEnc","NoSuchAlgorithmException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: InvalidKeyException) {
//Log.d("TestingEnc","InvalidKeyException")
// Log.d("TestingEnc","InvalidKeyException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: BadPaddingException) {
// Log.d("TestingEnc","BadPaddingException")
@@ -76,11 +73,11 @@ object EncryptionHelper {
}
}
@Throws(CryptoException::class)
fun doCryptoDecrypt(
key: String, inputFile: InputStream?
) : String {
key: String,
inputFile: InputStream?
): String {
var data = ""
try {
val secretKey: Key =
@@ -91,25 +88,24 @@ object EncryptionHelper {
inputFile.use {
val inputStream = it
CipherInputStream(inputStream, cipher).use {
data = String(it.readBytes())
}
CipherInputStream(inputStream, cipher).use {
data = String(it.readBytes())
}
}
} catch (ex: NoSuchPaddingException) {
//Log.d("TestingEnc","NoSuchPaddingException")
// Log.d("TestingEnc","NoSuchPaddingException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: NoSuchAlgorithmException) {
//Log.d("TestingEnc","NoSuchAlgorithmException")
// Log.d("TestingEnc","NoSuchAlgorithmException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: InvalidKeyException) {
//Log.d("TestingEnc","InvalidKeyException")
// Log.d("TestingEnc","InvalidKeyException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: BadPaddingException) {
//Log.d("TestingEnc","BadPaddingException")
// Log.d("TestingEnc","BadPaddingException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: IllegalBlockSizeException) {
//Log.d("TestingEnc","IllegalBlockSizeException")
// Log.d("TestingEnc","IllegalBlockSizeException")
throw CryptoException("Error encrypting/decrypting file", ex)
} catch (ex: IOException) {
// Log.d("TestingEnc","IOException")
@@ -119,9 +115,7 @@ object EncryptionHelper {
return data
}
fun encryptPassword(message: String, password: String): String{
fun encryptPassword(message: String, password: String): String {
/* Encrypt the message. */
var cipher: Cipher? = null
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
@@ -147,11 +141,8 @@ object EncryptionHelper {
return String(cipher.doFinal(encryptedMessage.toByteArray()))
}
@Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
fun generateKey(passLockKey: String): SecretKey {
return SecretKeySpec(passLockKey.encodeToByteArray(), "AES")
}
}
}

View File

@@ -17,14 +17,13 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun getDb(@ApplicationContext context: Context): AppDatabase{
fun getDb(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
@@ -44,12 +43,9 @@ object AppModule {
.build()
}
@Provides
@Singleton
fun getSharedPre(@ApplicationContext context: Context): SharedPreferences{
fun getSharedPre(@ApplicationContext context: Context): SharedPreferences {
return MySharedPreferences(context).sharedPref
}
}
}

View File

@@ -2,7 +2,6 @@ package com.yogeshpaliyal.keypass.listener
import android.view.View
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -12,4 +11,4 @@ import android.view.View
interface AccountsClickListener<T> {
fun onItemClick(view: View, model: T)
fun onCopyClicked(model: T)
}
}

View File

@@ -2,7 +2,6 @@ package com.yogeshpaliyal.keypass.listener
import android.view.View
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -11,4 +10,4 @@ import android.view.View
*/
interface UniversalClickListener<T> {
fun onItemClick(view: View, model: T)
}
}

View File

@@ -57,7 +57,6 @@ class BasicService : AutofillService() {
return
}
// Create the base response
val response = FillResponse.Builder()
@@ -129,8 +128,9 @@ class BasicService : AutofillService() {
fields[hint] = id
} else {
Log.v(
TAG, "Ignoring hint '" + hint + "' on " + id
+ " because it was already set"
TAG,
"Ignoring hint '" + hint + "' on " + id +
" because it was already set"
)
}
}
@@ -173,9 +173,9 @@ class BasicService : AutofillService() {
text: CharSequence
): RemoteViews {
val presentation = RemoteViews(packageName, R.layout.multidataset_service_list_item)
presentation.setTextViewText(R.id.text, "paliyal"+text)
presentation.setTextViewText(R.id.text, "paliyal" + text)
presentation.setImageViewResource(R.id.icon, R.mipmap.ic_launcher)
return presentation
}
}
}
}

View File

@@ -1,17 +1,10 @@
package com.yogeshpaliyal.keypass.service
import android.app.assist.AssistStructure
import android.app.assist.AssistStructure.ViewNode
import android.os.Build
import android.os.CancellationSignal
import android.service.autofill.*
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.widget.RemoteViews
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -24,21 +17,13 @@ class MyAutoFillService : AutofillService() {
private val TAG = "MyAutoFillService"
private val NUMBER_DATASETS = 4
override fun onFillRequest(
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
}
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
}
}
}

View File

@@ -12,25 +12,23 @@ import com.yogeshpaliyal.keypass.ui.nav.DashboardActivity
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.Executor
@AndroidEntryPoint
class AuthenticationActivity : AppCompatActivity() {
private lateinit var binding : ActivityAuthenticationBinding
private lateinit var binding: ActivityAuthenticationBinding
private lateinit var executor: Executor
private lateinit var biometricPrompt: BiometricPrompt
private lateinit var promptInfo: BiometricPrompt.PromptInfo
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAuthenticationBinding.inflate(layoutInflater)
setContentView(binding.root)
executor = ContextCompat.getMainExecutor(this)
biometricPrompt = BiometricPrompt(this, executor,
biometricPrompt = BiometricPrompt(
this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
@@ -42,7 +40,7 @@ class AuthenticationActivity : AppCompatActivity() {
"Authentication error: $errString", Toast.LENGTH_SHORT
)
.show()
// finish()
// finish()
}
override fun onAuthenticationSucceeded(
@@ -61,8 +59,8 @@ class AuthenticationActivity : AppCompatActivity() {
)
.show()
}
})
}
)
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.app_name))
@@ -78,16 +76,13 @@ class AuthenticationActivity : AppCompatActivity() {
binding.btnRetry.setOnClickListener {
biometricPrompt.authenticate(promptInfo)
}
}
private fun onAuthenticated(){
// binding.passCodeView.isVisible = false
private fun onAuthenticated() {
// binding.passCodeView.isVisible = false
val dashboardIntent = Intent(this, DashboardActivity::class.java)
startActivity(dashboardIntent)
finish()
}
}
}

View File

@@ -58,10 +58,10 @@ class BackupActivity : AppCompatActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var sp : SharedPreferences
lateinit var sp: SharedPreferences
@Inject
lateinit var appDb : AppDatabase
lateinit var appDb: AppDatabase
private val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
@@ -102,22 +102,22 @@ class BackupActivity : AppCompatActivity() {
findPreference<Preference>("start_backup")?.isVisible = isBackupEnabled.not()
findPreference<Preference>("create_backup")?.isVisible = isBackupEnabled
findPreference<Preference>("create_backup")?.summary = getString(R.string.last_backup_date,getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa"))
findPreference<Preference>("create_backup")?.summary = getString(R.string.last_backup_date, getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa"))
findPreference<Preference>("backup_folder")?.isVisible = isBackupEnabled
val directory = URLDecoder.decode(getBackupDirectory(sp),"utf-8").split("/")
val directory = URLDecoder.decode(getBackupDirectory(sp), "utf-8").split("/")
val folderName = directory.get(directory.lastIndex)
findPreference<Preference>("backup_folder")?.summary = folderName
findPreference<Preference>("settings_verify_key_phrase")?.isVisible = false
findPreference<Preference>("stop_backup")?.isVisible = isBackupEnabled
}
private fun startBackup(){
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
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
try {
@@ -129,7 +129,7 @@ class BackupActivity : AppCompatActivity() {
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){
if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val contentResolver = context?.contentResolver
val selectedDirectory = data?.data
if (contentResolver != null && selectedDirectory != null) {
@@ -138,13 +138,13 @@ class BackupActivity : AppCompatActivity() {
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
setBackupDirectory(sp,selectedDirectory.toString())
setBackupDirectory(sp, selectedDirectory.toString())
backup(selectedDirectory)
}
}
}
private fun backup(selectedDirectory: Uri){
private fun backup(selectedDirectory: Uri) {
val keyPair = getOrCreateBackupKey(sp)
@@ -155,11 +155,12 @@ class BackupActivity : AppCompatActivity() {
lifecycleScope.launch {
context?.contentResolver?.let {
appDb.createBackup(keyPair.second,
appDb.createBackup(
keyPair.second,
it,
tempFile?.uri
)
setBackupTime(sp,System.currentTimeMillis())
setBackupTime(sp, System.currentTimeMillis())
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey(sp).second
@@ -176,9 +177,10 @@ class BackupActivity : AppCompatActivity() {
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
getString(R.string.yes)
) { dialog, which -> dialog?.dismiss()
) { dialog, which ->
dialog?.dismiss()
}.show()
}else{
} else {
Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
}
}
@@ -187,20 +189,19 @@ class BackupActivity : AppCompatActivity() {
}
}
private fun changeBackupFolder(){
private fun changeBackupFolder() {
startBackup()
}
private fun verifyKeyPhrase(){
private fun verifyKeyPhrase() {
Toast.makeText(context, getString(R.string.coming_soon), Toast.LENGTH_SHORT).show()
}
private fun stopBackup(){
private fun stopBackup() {
clearBackupKey(sp)
setBackupDirectory(sp,"")
setBackupTime(sp,-1)
setBackupDirectory(sp, "")
setBackupTime(sp, -1)
updateItems()
}
}
}
}

View File

@@ -4,7 +4,6 @@ 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.lifecycle.Observer
@@ -20,7 +19,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* yogeshpaliyal.foss@gmail.com
@@ -32,7 +30,6 @@ class DetailActivity : AppCompatActivity() {
lateinit var binding: FragmentDetailBinding
@Inject
lateinit var appDb: AppDatabase
@@ -50,7 +47,6 @@ class DetailActivity : AppCompatActivity() {
private val mViewModel by viewModels<DetailViewModel>()
private val accountId by lazy {
intent?.extras?.getLong(ARG_ACCOUNT_ID) ?: -1
}
@@ -62,9 +58,12 @@ class DetailActivity : AppCompatActivity() {
binding.lifecycleOwner = this
mViewModel.loadAccount(accountId)
mViewModel.accountModel.observe(this, Observer {
binding.accountData = it
})
mViewModel.accountModel.observe(
this,
Observer {
binding.accountData = it
}
)
if (accountId > 0) {
binding.bottomAppBar.replaceMenu(R.menu.bottom_app_bar_detail)
@@ -78,8 +77,6 @@ class DetailActivity : AppCompatActivity() {
}
}
binding.bottomAppBar.setNavigationOnClickListener {
onBackPressed()
}
@@ -92,7 +89,6 @@ class DetailActivity : AppCompatActivity() {
return@setOnMenuItemClickListener false
}
binding.btnSave.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
val model = mViewModel.accountModel.value
@@ -126,12 +122,10 @@ class DetailActivity : AppCompatActivity() {
.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)
}
}
}

View File

@@ -11,7 +11,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -30,8 +29,5 @@ class DetailViewModel @Inject constructor(application: Application, val appDb: A
appDb.getDao().getAccount(accountId) ?: AccountModel()
)
}
}
}
}

View File

@@ -1,6 +1,5 @@
package com.yogeshpaliyal.keypass.ui.generate
import android.R.attr.label
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
@@ -12,10 +11,9 @@ import com.yogeshpaliyal.keypass.databinding.ActivityGeneratePasswordBinding
import com.yogeshpaliyal.keypass.utils.PasswordGenerator
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class GeneratePasswordActivity : AppCompatActivity() {
private lateinit var binding : ActivityGeneratePasswordBinding
private lateinit var binding: ActivityGeneratePasswordBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityGeneratePasswordBinding.inflate(layoutInflater)
@@ -28,15 +26,14 @@ class GeneratePasswordActivity : AppCompatActivity() {
}
binding.tilPassword.setEndIconOnClickListener {
val clipboard= getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
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(){
private fun generatePassword() {
val password = PasswordGenerator(
binding.sliderPasswordLength.value.toInt(), binding.cbCapAlphabets.isChecked,
binding.cbLowerAlphabets.isChecked,
@@ -46,4 +43,4 @@ class GeneratePasswordActivity : AppCompatActivity() {
binding.etPassword.setText(password)
}
}
}

View File

@@ -10,7 +10,6 @@ import com.yogeshpaliyal.keypass.data.AccountModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -28,7 +27,6 @@ class DashboardViewModel @Inject constructor(application: Application, val appDb
MutableLiveData<String>()
}
val mediator = MediatorLiveData<LiveData<List<AccountModel>>>()
init {
@@ -40,7 +38,5 @@ class DashboardViewModel @Inject constructor(application: Application, val appDb
mediator.postValue(appDb.getDao().getAllAccounts(keyword.value, tag.value))
}
mediator.postValue(appDb.getDao().getAllAccounts(keyword.value, tag.value))
}
}
}

View File

@@ -15,14 +15,12 @@ import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.data.AccountModel
import com.yogeshpaliyal.keypass.databinding.FragmentHomeBinding
import com.yogeshpaliyal.keypass.listener.AccountsClickListener
import com.yogeshpaliyal.keypass.listener.UniversalClickListener
import com.yogeshpaliyal.keypass.ui.detail.DetailActivity
import com.yogeshpaliyal.universal_adapter.adapter.UniversalAdapterViewType
import com.yogeshpaliyal.universal_adapter.adapter.UniversalRecyclerAdapter
import com.yogeshpaliyal.universal_adapter.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -76,7 +74,6 @@ class HomeFragment : Fragment() {
val observer = Observer<List<AccountModel>> {
mAdapter.updateData(Resource.success(it))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -84,10 +81,13 @@ class HomeFragment : Fragment() {
binding.recyclerView.adapter = mAdapter.getAdapter()
mViewModel.mediator.observe(viewLifecycleOwner, Observer {
it.removeObserver(observer)
it.observe(viewLifecycleOwner, observer)
})
mViewModel.mediator.observe(
viewLifecycleOwner,
Observer {
it.removeObserver(observer)
it.observe(viewLifecycleOwner, observer)
}
)
/* lifecycleScope.launch() {
mViewModel.result
@@ -97,8 +97,5 @@ class HomeFragment : Fragment() {
}
}
}*/
}
}
}

View File

@@ -21,11 +21,8 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.from
import com.google.android.material.shape.MaterialShapeDrawable
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.databinding.FragmentBottomNavDrawerBinding
import com.yogeshpaliyal.keypass.ui.detail.DetailViewModel
import com.yogeshpaliyal.keypass.utils.initViewModel
import com.yogeshpaliyal.keypass.utils.themeColor
import dagger.hilt.android.AndroidEntryPoint
import kotlin.LazyThreadSafetyMode.NONE
/**
@@ -36,8 +33,6 @@ class BottomNavDrawerFragment :
Fragment(),
NavigationAdapter.NavigationAdapterListener {
private lateinit var binding: FragmentBottomNavDrawerBinding
private val behavior: BottomSheetBehavior<FrameLayout> by lazy(NONE) {
@@ -46,14 +41,10 @@ class BottomNavDrawerFragment :
private val mViewModel by viewModels<BottomNavViewModel>()
private val bottomSheetCallback = BottomNavigationDrawerCallback()
private val navigationListeners: MutableList<NavigationAdapter.NavigationAdapterListener> =
mutableListOf()
mutableListOf()
private val foregroundShapeDrawable: MaterialShapeDrawable by lazy(NONE) {
val foregroundContext = binding.foregroundContainer.context
@@ -82,7 +73,6 @@ class BottomNavDrawerFragment :
}
}
private val closeDrawerOnBackPressed = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
close()
@@ -115,7 +105,7 @@ class BottomNavDrawerFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.run {
//backgroundContainer.background = backgroundShapeDrawable
// backgroundContainer.background = backgroundShapeDrawable
foregroundContainer.background = foregroundShapeDrawable
scrimView.setOnClickListener { close() }
@@ -134,7 +124,6 @@ class BottomNavDrawerFragment :
// 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.
@@ -145,8 +134,6 @@ class BottomNavDrawerFragment :
})
}
behavior.addBottomSheetCallback(bottomSheetCallback)
behavior.state = STATE_HIDDEN
@@ -157,11 +144,9 @@ class BottomNavDrawerFragment :
adapter.submitList(it)
}
mViewModel.setNavigationMenuItemChecked(0)
}
}
fun open() {
behavior.state = STATE_HALF_EXPANDED
}
@@ -182,8 +167,6 @@ class BottomNavDrawerFragment :
navigationListeners.add(listener)
}
override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
mViewModel.setNavigationMenuItemChecked(item.id)
close()
@@ -194,15 +177,13 @@ class BottomNavDrawerFragment :
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()
behavior.state == STATE_HIDDEN ||
behavior.state == STATE_HALF_EXPANDED ||
behavior.state == STATE_EXPANDED
|| behavior.state == STATE_COLLAPSED -> close()
}
}
}
}

View File

@@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -19,17 +18,17 @@ import javax.inject.Inject
* created on 31-01-2021 14:11
*/
@HiltViewModel
class BottomNavViewModel @Inject constructor (application: Application,val appDb: AppDatabase) : AndroidViewModel(application) {
class BottomNavViewModel @Inject constructor (application: Application, val appDb: AppDatabase) : AndroidViewModel(application) {
private val _navigationList: MutableLiveData<List<NavigationModelItem>> = MutableLiveData()
private val tagsDb = appDb.getDao().getTags()
private var tagsList : List<String> ?= null
private var tagsList: List<String> ? = null
val navigationList: LiveData<List<NavigationModelItem>>
get() = _navigationList
init {
postListUpdate()
postListUpdate()
viewModelScope.launch {
tagsDb.collect {
@@ -39,8 +38,6 @@ class BottomNavViewModel @Inject constructor (application: Application,val appDb
}
}
/**
* Set the currently selected menu item.
*
@@ -59,18 +56,13 @@ class BottomNavViewModel @Inject constructor (application: Application,val appDb
return updated
}
private fun postListUpdate() {
val newList = if(tagsList.isNullOrEmpty().not()){
val newList = if (tagsList.isNullOrEmpty().not()) {
NavigationModel.navigationMenuItems + NavigationModelItem.NavDivider("Tags") + (tagsList?.filter { it != null }?.map { NavigationModelItem.NavEmailFolder(it) } ?: listOf())
}else{
} else {
NavigationModel.navigationMenuItems
}
_navigationList.value = newList
}
}
}

View File

@@ -97,4 +97,4 @@ class BottomNavigationDrawerCallback : BottomSheetBehavior.BottomSheetCallback()
fun removeOnStateChangedAction(action: OnStateChangedAction): Boolean {
return onStateChangedActions.remove(action)
}
}
}

View File

@@ -27,13 +27,13 @@ import com.yogeshpaliyal.keypass.ui.settings.MySettingsFragmentDirections
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DashboardActivity : AppCompatActivity(),
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
}
@@ -51,11 +51,11 @@ class DashboardActivity : AppCompatActivity(),
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
);
)
binding = ActivityDashboardBinding.inflate(layoutInflater)
setContentView(binding.root)
//setSupportActionBar(binding.bottomAppBar)
// setSupportActionBar(binding.bottomAppBar)
binding.lifecycleOwner = this
binding.viewModel = mViewModel
@@ -64,11 +64,9 @@ class DashboardActivity : AppCompatActivity(),
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()) {
@@ -78,7 +76,6 @@ class DashboardActivity : AppCompatActivity(),
}
}*/
binding.btnAdd.setOnClickListener {
currentNavigationFragment?.apply {
exitTransition = MaterialElevationScale(false).apply {
@@ -89,25 +86,26 @@ class DashboardActivity : AppCompatActivity(),
}
}
DetailActivity.start(this)
DetailActivity.start(this)
}
bottomNavDrawer.apply {
//addOnSlideAction(HalfClockwiseRotateSlideAction(binding.bottomAppBar))
// 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()
}
)
})
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)
@@ -120,8 +118,6 @@ class DashboardActivity : AppCompatActivity(),
}
setOnMenuItemClickListener(this@DashboardActivity)
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
@@ -135,8 +131,6 @@ class DashboardActivity : AppCompatActivity(),
return true
}
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
@@ -154,8 +148,8 @@ class DashboardActivity : AppCompatActivity(),
override fun onNavMenuItemClicked(item: NavigationModelItem.NavMenuItem) {
// Swap the list of emails for the given mailbox
//navigateToHome(item.titleRes, item.mailbox)
when(item.id){
// navigateToHome(item.titleRes, item.mailbox)
when (item.id) {
NavigationModel.GENERATE_PASSWORD -> {
val intent = Intent(this, GeneratePasswordActivity::class.java)
startActivity(intent)
@@ -174,7 +168,6 @@ class DashboardActivity : AppCompatActivity(),
bottomNavDrawer.close()
}
private fun hideBottomAppBar() {
binding.run {
bottomAppBar.performHide()
@@ -211,21 +204,20 @@ class DashboardActivity : AppCompatActivity(),
val dest = destination ?: findNavController(R.id.nav_host_fragment).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
// 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
// btnAdd.contentDescription = getString(R.string.fab_compose_email_content_description)
// bottomAppBarTitle.visibility = View.VISIBLE
bottomAppBar.performShow()
btnAdd.show()
}
}
}
}

View File

@@ -86,4 +86,4 @@ class NavigationAdapter(
) {
holder.bind(getItem(position))
}
}
}

View File

@@ -1,7 +1,5 @@
package com.yogeshpaliyal.keypass.ui.nav
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.yogeshpaliyal.keypass.R
/**
@@ -12,7 +10,6 @@ object NavigationModel {
const val HOME = 0
const val GENERATE_PASSWORD = 1
var navigationMenuItems = mutableListOf(
NavigationModelItem.NavMenuItem(
id = HOME,
@@ -27,11 +24,4 @@ object NavigationModel {
checked = false,
)
)
}

View File

@@ -50,14 +50,14 @@ sealed class 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 NavMenuItem && newItem is NavMenuItem ->
oldItem.icon == newItem.icon &&
oldItem.titleRes == newItem.titleRes &&
oldItem.checked == newItem.checked
oldItem is NavEmailFolder && newItem is NavEmailFolder ->
StringDiffUtil.areContentsTheSame(oldItem.category, newItem.category)
else -> false
}
}
}
}
}

View File

@@ -1,6 +1,5 @@
package com.yogeshpaliyal.keypass.ui.nav
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.yogeshpaliyal.keypass.databinding.NavDividerItemLayoutBinding
@@ -11,7 +10,7 @@ sealed class NavigationViewHolder<T : NavigationModelItem>(
view: View
) : RecyclerView.ViewHolder(view) {
abstract fun bind(navItem : T)
abstract fun bind(navItem: T)
class NavMenuItemViewHolder(
private val binding: NavMenuItemLayoutBinding,
@@ -49,4 +48,4 @@ sealed class NavigationViewHolder<T : NavigationModelItem>(
}
}
}
}
}

View File

@@ -1,6 +1,5 @@
package com.yogeshpaliyal.keypass.ui.settings
import android.R.attr.label
import android.app.Activity
import android.content.*
import android.net.Uri
@@ -25,24 +24,23 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class MySettingsFragment : PreferenceFragmentCompat() {
private val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
private val CHOOSE_RESTORE_FILE_REQUEST_CODE = 26213
@Inject
lateinit var appDb : AppDatabase
lateinit var appDb: AppDatabase
@Inject
lateinit var sp : SharedPreferences
lateinit var sp: SharedPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when(preference?.key){
when (preference?.key) {
"feedback" -> {
context?.email(getString(R.string.feedback_to_keypass), "yogeshpaliyal.foss@gmail.com")
return true
@@ -73,20 +71,20 @@ class MySettingsFragment : PreferenceFragmentCompat() {
return super.onPreferenceTreeClick(preference)
}
private fun selectBackupDirectory(){
private fun selectBackupDirectory() {
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
context?.let {
if(it.canUserAccessBackupDirectory(sp)){
if (it.canUserAccessBackupDirectory(sp)) {
backup(selectedDirectory)
}else{
} 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.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
try {
@@ -96,30 +94,28 @@ class MySettingsFragment : PreferenceFragmentCompat() {
}
}
}
}
private fun selectRestoreFile(){
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
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
try {
startActivityForResult(intent, CHOOSE_RESTORE_FILE_REQUEST_CODE)
}catch (e: Exception){
} 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){
if (requestCode == CHOOSE_BACKUPS_LOCATION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val contentResolver = context?.contentResolver
val selectedDirectory = data?.data
if (contentResolver != null && selectedDirectory != null) {
@@ -128,10 +124,10 @@ class MySettingsFragment : PreferenceFragmentCompat() {
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
setBackupDirectory(sp,selectedDirectory.toString())
setBackupDirectory(sp, selectedDirectory.toString())
backup(selectedDirectory)
}
} else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK){
} 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) {
@@ -148,7 +144,7 @@ class MySettingsFragment : PreferenceFragmentCompat() {
"Restore"
) { dialog, which ->
lifecycleScope.launch {
val result = appDb.restoreBackup(binding.etKeyPhrase.text.toString(),contentResolver, selectedFile)
val result = appDb.restoreBackup(binding.etKeyPhrase.text.toString(), contentResolver, selectedFile)
if (result) {
dialog?.dismiss()
Toast.makeText(
@@ -156,7 +152,7 @@ class MySettingsFragment : PreferenceFragmentCompat() {
getString(R.string.backup_restored),
Toast.LENGTH_SHORT
).show()
}else{
} else {
Toast.makeText(
context,
getString(R.string.invalid_keyphrase),
@@ -165,13 +161,11 @@ class MySettingsFragment : PreferenceFragmentCompat() {
}
}
}.show()
}
}
}
fun backup(selectedDirectory: Uri){
fun backup(selectedDirectory: Uri) {
val keyPair = getOrCreateBackupKey(sp)
@@ -182,10 +176,11 @@ class MySettingsFragment : PreferenceFragmentCompat() {
lifecycleScope.launch {
context?.contentResolver?.let {
appDb.createBackup(keyPair.second,
it,
tempFile?.uri
)
appDb.createBackup(
keyPair.second,
it,
tempFile?.uri
)
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey(sp).second
@@ -199,14 +194,13 @@ class MySettingsFragment : PreferenceFragmentCompat() {
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(
"Yes"
) { dialog, which -> dialog?.dismiss()
) { dialog, which ->
dialog?.dismiss()
}.show()
}else{
} else {
Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
}
}
}
}
}
}

View File

@@ -132,4 +132,4 @@ fun Float.normalize(
return outputMin * (1 - (this - inputMin) / (inputMax - inputMin)) +
outputMax * ((this - inputMin) / (inputMax - inputMin))
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.view.autofill.AutofillManager
import androidx.core.content.ContextCompat.getSystemService
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -12,9 +11,8 @@ import androidx.core.content.ContextCompat.getSystemService
* created on 31-01-2021 15:27
*/
fun Context?.getAutoFillService() = if (this != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
getSystemService(this, AutofillManager::class.java)
} else {
null
}
fun Context?.getAutoFillService() = if (this != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
getSystemService(this, AutofillManager::class.java)
} else {
null
}

View File

@@ -7,7 +7,6 @@ import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import java.security.SecureRandom
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -21,21 +20,21 @@ fun getRandomString(sizeOfRandomString: Int): String {
val random = SecureRandom()
val sb = StringBuilder(sizeOfRandomString)
for (i in 0 until sizeOfRandomString) sb.append(
ALLOWED_CHARACTERS[random.nextInt(
ALLOWED_CHARACTERS.length
)]
ALLOWED_CHARACTERS[
random.nextInt(
ALLOWED_CHARACTERS.length
)
]
)
return sb.toString()
}
fun Context.canUserAccessBackupDirectory(sp : SharedPreferences): Boolean {
fun Context.canUserAccessBackupDirectory(sp: SharedPreferences): Boolean {
val backupDirectoryUri = getUri(getBackupDirectory(sp)) ?: 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)) {

View File

@@ -15,31 +15,29 @@
package com.yogeshpaliyal.keypass.utils
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
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.ImageView
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.chip.Chip
import com.google.android.material.elevation.ElevationOverlayProvider
@BindingAdapter(
"popupElevationOverlay"
)
fun Spinner.bindPopupElevationOverlay(popupElevationOverlay: Float) {
setPopupBackgroundDrawable(ColorDrawable(
ElevationOverlayProvider(context)
.compositeOverlayWithThemeSurfaceColorIfNeeded(popupElevationOverlay)
))
setPopupBackgroundDrawable(
ColorDrawable(
ElevationOverlayProvider(context)
.compositeOverlayWithThemeSurfaceColorIfNeeded(popupElevationOverlay)
)
)
}
@BindingAdapter(
@@ -67,8 +65,6 @@ fun TextView.bindDrawables(
)
}
@BindingAdapter("goneIf")
fun View.bindGoneIf(gone: Boolean) {
visibility = if (gone) {
@@ -219,4 +215,4 @@ fun View.requestApplyInsetsWhenAttached() {
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
}

View File

@@ -16,7 +16,6 @@
package com.yogeshpaliyal.keypass.utils
import android.app.Activity
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
@@ -46,4 +45,4 @@ class ContentViewBindingDelegate<in R : AppCompatActivity, out T : ViewDataBindi
fun <R : AppCompatActivity, T : ViewDataBinding> contentView(
@LayoutRes layoutRes: Int
): ContentViewBindingDelegate<R, T> = ContentViewBindingDelegate(layoutRes)
): ContentViewBindingDelegate<R, T> = ContentViewBindingDelegate(layoutRes)

View File

@@ -67,4 +67,4 @@ fun Context.themeInterpolator(@AttrRes attr: Int): Interpolator {
fun Context.getDrawableOrNull(@DrawableRes id: Int?): Drawable? {
return if (id == null || id == 0) null else AppCompatResources.getDrawable(this, id)
}
}

View File

@@ -3,7 +3,6 @@ package com.yogeshpaliyal.keypass.utils
import java.text.SimpleDateFormat
import java.util.*
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -11,11 +10,9 @@ import java.util.*
* created on 23-03-2021 22:30
*/
fun Long.formatCalendar(dateTimeFormat: String?): String? {
val calendar: Calendar = Calendar.getInstance()
calendar.timeInMillis = this
val simpleDateFormat = SimpleDateFormat(dateTimeFormat, Locale.US)
return simpleDateFormat.format(calendar.getTime())
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -14,7 +13,6 @@ import android.net.Uri
@JvmName("IntentHelper")
fun Context.email(
chooserTitle: String,
email: String = "",
@@ -33,8 +31,7 @@ fun Context.email(
if (text.isNotEmpty())
intent.putExtra(Intent.EXTRA_TEXT, text)
startActivity(Intent.createChooser(intent, chooserTitle))
startActivity(Intent.createChooser(intent, chooserTitle))
}
fun Context.makeCall(chooserTitle: String, number: String): Boolean {
@@ -49,7 +46,6 @@ fun Context.makeCall(chooserTitle: String, number: String): Boolean {
}
}
fun Context.sendSMS(chooserTitle: String, number: String, text: String = ""): Boolean {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:$number"))
@@ -62,7 +58,6 @@ fun Context.sendSMS(chooserTitle: String, number: String, text: String = ""): Bo
}
}
fun Context.share(chooserTitle: String, text: String): Boolean {
try {
val intent = Intent(Intent.ACTION_SEND)
@@ -76,7 +71,6 @@ fun Context.share(chooserTitle: String, text: String): Boolean {
}
}
fun Context.navigate(address: String) {
val gmmIntentUri =
Uri.parse("google.navigation:q=$address")
@@ -86,4 +80,3 @@ fun Context.navigate(address: String) {
startActivity(mapIntent)
}
}

View File

@@ -3,7 +3,6 @@ package com.yogeshpaliyal.keypass.utils
import android.util.Log
import com.yogeshpaliyal.keypass.BuildConfig
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -47,4 +46,4 @@ fun Any?.logV(tag: String?) {
fun Any?.logW(tag: String?) {
if (BuildConfig.DEBUG) Log.w(tag, this.toString())
}
}

View File

@@ -7,8 +7,8 @@ import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.yogeshpaliyal.keypass.MyApplication
class MySharedPreferences(context : Context) {
private val masterKeyAlias = MasterKey.Builder(MyApplication.instance).also {
class MySharedPreferences(context: Context) {
private val masterKeyAlias = MasterKey.Builder(MyApplication.instance).also {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
// this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC
@@ -23,11 +23,10 @@ class MySharedPreferences(context : Context) {
.build()
it.setKeyGenParameterSpec(spec)
}
//it.setUserAuthenticationRequired(true)
// it.setUserAuthenticationRequired(true)
}
.build()
val sharedPref = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
@@ -35,4 +34,4 @@ class MySharedPreferences(context : Context) {
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
}

View File

@@ -1,37 +1,41 @@
package com.yogeshpaliyal.keypass.utils
class PasswordGenerator(private var length: Int, private var includeUpperCaseLetters : Boolean,
private var includeLowerCaseLetters : Boolean,
private var includeSymbols : Boolean, private var includeNumbers: Boolean) {
class PasswordGenerator(
private var length: Int,
private var includeUpperCaseLetters: Boolean,
private var includeLowerCaseLetters: Boolean,
private var includeSymbols: Boolean,
private var includeNumbers: Boolean
) {
constructor() : this(10,true, true, true, true)
constructor() : this(10, true, true, true, true)
private val UPPER_CASE = 0
private val LOWER_CASE = 1
private val NUMBERS = 2
private val SYMBOLS = 3
public fun generatePassword() : String {
public fun generatePassword(): String {
var password = ""
val list = ArrayList<Int>()
if(includeUpperCaseLetters)
val list = ArrayList<Int>()
if (includeUpperCaseLetters)
list.add(UPPER_CASE)
if(includeLowerCaseLetters)
if (includeLowerCaseLetters)
list.add(LOWER_CASE)
if(includeNumbers)
if (includeNumbers)
list.add(NUMBERS)
if(includeSymbols)
if (includeSymbols)
list.add(SYMBOLS)
for(i in 1..length){
for (i in 1..length) {
val choice = list.random()
when(choice){
when (choice) {
UPPER_CASE -> password += ('A'..'Z').random().toString()
LOWER_CASE -> password += ('a'..'z').random().toString()
NUMBERS -> password += ('0'..'9').random().toString()
SYMBOLS -> password += listOf('!','@','#','$','%','&','*','+','=','-','~','?','/','_').random().toString()
SYMBOLS -> password += listOf('!', '@', '#', '$', '%', '&', '*', '+', '=', '-', '~', '?', '/', '_').random().toString()
}
}
return password
}
}
}

View File

@@ -1,13 +1,7 @@
package com.yogeshpaliyal.keypass.utils
import android.content.SharedPreferences
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.yogeshpaliyal.keypass.MyApplication
/*
* @author Yogesh Paliyal
@@ -16,13 +10,12 @@ import com.yogeshpaliyal.keypass.MyApplication
* created on 21-02-2021 11:18
*/
/**
* Pair
* 1st => true if key is created now & false if key is created previously
*
*/
fun getOrCreateBackupKey(sp: SharedPreferences,reset: Boolean = false): Pair<Boolean, String> {
fun getOrCreateBackupKey(sp: SharedPreferences, reset: Boolean = false): Pair<Boolean, String> {
return if (sp.contains(BACKUP_KEY) && reset.not()) {
Pair(false, sp.getString(BACKUP_KEY, "") ?: "")
@@ -40,17 +33,15 @@ fun clearBackupKey(sp: SharedPreferences) {
sp.edit {
remove(BACKUP_KEY)
}
}
fun setBackupDirectory(sp: SharedPreferences,string: String) {
fun setBackupDirectory(sp: SharedPreferences, string: String) {
sp.edit {
putString(BACKUP_DIRECTORY, string)
}
}
fun setBackupTime(sp: SharedPreferences,time: Long) {
fun setBackupTime(sp: SharedPreferences, time: Long) {
sp.edit {
putLong(BACKUP_DATE_TIME, time)
}
@@ -66,4 +57,4 @@ fun getBackupTime(sp: SharedPreferences,): Long {
private const val BACKUP_KEY = "backup_key"
private const val BACKUP_DIRECTORY = "backup_directory"
private const val BACKUP_DATE_TIME = "backup_date_time"
private const val BACKUP_DATE_TIME = "backup_date_time"

View File

@@ -2,7 +2,6 @@ package com.yogeshpaliyal.keypass.utils
import java.util.*
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
@@ -10,4 +9,4 @@ import java.util.*
* created on 22-01-2021 23:14
*/
fun getRandomString()= UUID.randomUUID().toString()
fun getRandomString() = UUID.randomUUID().toString()

View File

@@ -17,19 +17,8 @@
package com.yogeshpaliyal.keypass.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import androidx.annotation.Px
import androidx.core.graphics.applyCanvas
import androidx.core.view.ViewCompat
import androidx.core.view.forEach
@Suppress("DEPRECATION")
fun TextView.setTextAppearanceCompat(context: Context, resId: Int) {

View File

@@ -32,4 +32,3 @@ fun <T : ViewModel> ViewModelStoreOwner.initViewModel(viewModel: Class<T>): T =
this,
ViewModelFactory()
).get(viewModel)

View File

@@ -0,0 +1,62 @@
<resources>
<string name="generate_password">生成密码</string>
<string name="home">主页</string>
<string name="delete">删除</string>
<string name="generate_shortcut_short_label">生成密码</string>
<string name="backup_restored">备份密码</string>
<string name="invalid_keyphrase">无效密码</string>
<string name="backup_completed">备份完成</string>
<!-- Sync Preferences -->
<string name="credentials_backups">备份</string>
<string name="credentials_backups_desc">把密码备份到外部存储</string>
<string name="restore_credentials">恢复备份</string>
<string name="restore_credentials_desc">从备份中恢复</string>
<string name="send_feedback">发送反馈</string>
<string name="send_feedback_desc">报告技术问题或建议新功能</string>
<string name="share">分享</string>
<string name="share_desc">向别人分享该应用</string>
<string name="password_length">密码长度</string>
<string name="password">密码</string>
<string name="uppercase_alphabets">大写字母</string>
<string name="lowercase_alphabets">小写字母</string>
<string name="symbols">标点符号</string>
<string name="numbers">数字</string>
<string name="copied_to_clipboard">已复制到剪切板</string>
<string name="login_to_enter_keypass">输入密码以进入 KeyPass</string>
<string name="authentication_failed">身份认证失败</string>
<string name="last_backup_date">上次备份: %s</string>
<string name="coming_soon">敬请期待</string>
<string name="delete_account_title">您确定吗?</string>
<string name="delete_account_msg">您确定要删除本条目吗?此操作不可撤销。</string>
<string name="cancel">Cancel</string>
<string name="feedback_to_keypass">向 KeyPass 发送反馈</string>
<string name="share_keypass">分享 KeyPass</string>
<string name="message_no_accounts">这里空空如也。按下下方的加号以新建。</string>
<string name="turn_off_backup">关闭备份</string>
<string name="verify_keyphrase">验证备份密码</string>
<string name="verify_keyphrase_message">测试备份密码并验证其匹配</string>
<string name="backup_folder">备份文件夹</string>
<string name="backup">备份</string>
<string name="turn_on_backup">开启备份</string>
<string name="backup_desc">备份使用密码加密并存储在您的设备上</string>
<string name="yes">确定</string>
<string name="create_backup">创建备份</string>
<string name="alert">警告</string>
<string name="copy_keypharse_msg">请立即复制或在纸上写下下面的密码。KeyPass 将不会再次显示该密码。</string>
<string name="help">帮助</string>
<string name="security">安全</string>
<string name="search">搜索</string>
<string name="account_name">账户名称</string>
<string name="username_email_phone">用户名/邮箱/手机号</string>
<string name="tags_comma_separated_optional">标签(选填)</string>
<string name="website_url_optional">网站(选填)</string>
<string name="notes_optional">附注(选填)</string>
</resources>

View File

@@ -1,8 +1,7 @@
package com.yogeshpaliyal.keypass
import org.junit.Test
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
@@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
}

View File

@@ -2,22 +2,21 @@
buildscript {
ext{
kotlin_version = "1.4.21"
kotlin_version = "1.5.30"
lifecycle_version = "2.3.1"
room_version = "2.3.0"
navigation_version = '2.3.5'
ext.hilt_version = '2.35'
ext.hilt_version = '2.38.1'
}
repositories {
google()
jcenter()
maven { url "https://maven.localazy.com/repository/release/" }
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.3"
classpath 'com.android.tools.build:gradle:7.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.8'
classpath 'com.google.gms:google-services:4.3.10'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
@@ -27,6 +26,9 @@ buildscript {
// in the individual module build.gradle files
}
}
plugins {
id 'com.diffplug.spotless' version '5.10.2'
}
allprojects {
repositories {
@@ -37,6 +39,30 @@ allprojects {
}
}
task clean(type: Delete) {
/*task clean(type: Delete) {
delete rootProject.buildDir
}*/
subprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
apply plugin: 'com.diffplug.spotless'
spotless {
kotlin {
target '**/*.kt'
targetExclude("$buildDir/**/*.kt")
targetExclude('bin/**/*.kt')
java.util.HashMap<String, String> map = new HashMap<>();
map.put("disabled_rules","no-wildcard-imports")
ktlint("0.40.0").userData(map)
// licenseHeaderFile rootProject.file('spotless/copyright.kt')
}
}
}

View File

@@ -1,6 +1,6 @@
#Sat May 22 13:20:40 IST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME