added Dipendency Injection (hilt) and custom password generate length

This commit is contained in:
Yogesh Paliyal
2021-05-22 15:34:28 +05:30
parent f3979304e5
commit a96ac22df2
27 changed files with 300 additions and 228 deletions

View File

@@ -1,22 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

2
.idea/compiler.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
<bytecodeTargetLevel target="11" />
</component>
</project>

5
.idea/gradle.xml generated
View File

@@ -4,10 +4,10 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8 (2)" />
<option name="gradleJvm" value="JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -15,7 +15,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -3,6 +3,7 @@ plugins {
id 'kotlin-android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
id("dagger.hilt.android.plugin")
}
@@ -74,17 +75,17 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation ('androidx.appcompat:appcompat:1.2.0')
implementation ('androidx.appcompat:appcompat:1.3.0')
implementation "androidx.documentfile:documentfile:1.0.1"
// ViewModel
@@ -102,7 +103,7 @@ dependencies {
implementation "androidx.room:room-ktx:$room_version"
implementation 'com.github.yogeshpaliyal:Android-Universal-Recycler-View-Adapter:1.0.2'
implementation 'com.github.yogeshpaliyal:Android-Universal-Recycler-View-Adapter:2.0.0'
implementation "androidx.biometric:biometric:1.1.0"
@@ -116,4 +117,9 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
// dependency injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
}

View File

@@ -12,7 +12,7 @@ import com.yogeshpaliyal.keypass.utils.getRandomString
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* yogeshpaliyal.foss@gmail.com
* https://techpaliyal.com
* created on 30-01-2021 20:37
*/
@@ -25,38 +25,4 @@ abstract class AppDatabase : RoomDatabase() {
// define DAO start
abstract fun getDao(): DbDao
// define DAO end
companion object {
private lateinit var _instance: AppDatabase
fun getInstance(): AppDatabase {
if (!this::_instance.isInitialized)
synchronized(this) {
_instance = Room.databaseBuilder(
MyApplication.instance,
AppDatabase::class.java,
MyApplication.instance.getString(R.string.app_name)
).addMigrations(object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `account` ADD COLUMN `unique_id` TEXT")
database.query("select id,unique_id from `account` where unique_id IS NULL")
?.use {
while (it.moveToNext()) {
val id = it.getInt(0)
database.execSQL("update `account` set `unique_id` = '${getRandomString()}' where `id` = '$id'")
}
}
}
})
.build()
}
return _instance
}
}
}

View File

@@ -1,14 +1,16 @@
package com.yogeshpaliyal.keypass
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* yogeshpaliyal.foss@gmail.com
* https://techpaliyal.com
* created on 22-01-2021 22:41
*/
@HiltAndroidApp
class MyApplication : Application() {
companion object{

View File

@@ -2,23 +2,14 @@ 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
import com.yogeshpaliyal.keypass.data.BackupData
import com.yogeshpaliyal.keypass.utils.getOrCreateBackupKey
import com.yogeshpaliyal.keypass.utils.getRandomString
import com.yogeshpaliyal.keypass.utils.logD
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
import java.io.File
import java.io.OutputStream
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
/*
@@ -28,29 +19,35 @@ import javax.crypto.SecretKey
* created on 20-02-2021 19:31
*/
suspend fun AppDatabase.createBackup(key: String,contentResolver : ContentResolver,fileUri: Uri?) = withContext(Dispatchers.IO){
fileUri ?: return@withContext false
val data = getDao().getAllAccounts().first()
suspend fun AppDatabase.createBackup(key: String, contentResolver: ContentResolver, fileUri: Uri?) =
withContext(Dispatchers.IO) {
fileUri ?: return@withContext false
val data = getDao().getAllAccounts().first()
val json = Gson().toJson(BackupData(this@createBackup.openHelper.readableDatabase.version, data))
val fileStream = contentResolver.openOutputStream(fileUri)
EncryptionHelper.doCryptoEncrypt(key,json, fileStream)
val json =
Gson().toJson(BackupData(this@createBackup.openHelper.readableDatabase.version, data))
val fileStream = contentResolver.openOutputStream(fileUri)
EncryptionHelper.doCryptoEncrypt(key, json, fileStream)
return@withContext true
}
return@withContext true
}
suspend fun AppDatabase.restoreBackup(key: String,contentResolver : ContentResolver,fileUri: Uri?) = withContext(Dispatchers.IO){
suspend fun AppDatabase.restoreBackup(
key: String,
contentResolver: ContentResolver,
fileUri: Uri?
) = withContext(Dispatchers.IO) {
fileUri ?: return@withContext false
val restoredFile = try {
EncryptionHelper.doCryptoDecrypt(key, contentResolver.openInputStream(fileUri))
}catch (e:Exception){
val restoredFile = try {
EncryptionHelper.doCryptoDecrypt(key, contentResolver.openInputStream(fileUri))
} catch (e: Exception) {
e.printStackTrace()
return@withContext false
}
val data = Gson().fromJson(restoredFile, BackupData::class.java)
if (data.version == 3){
if (data.version == 3) {
for (datum in data.data) {
datum.uniqueId = getRandomString()
}

View File

@@ -0,0 +1,55 @@
package com.yogeshpaliyal.keypass.di.module
import android.content.Context
import android.content.SharedPreferences
import androidx.room.Room
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.MyApplication
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.utils.MySharedPreferences
import com.yogeshpaliyal.keypass.utils.getRandomString
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
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{
return Room.databaseBuilder(
context,
AppDatabase::class.java,
MyApplication.instance.getString(R.string.app_name)
).addMigrations(object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `account` ADD COLUMN `unique_id` TEXT")
database.query("select id,unique_id from `account` where unique_id IS NULL")
?.use {
while (it.moveToNext()) {
val id = it.getInt(0)
database.execSQL("update `account` set `unique_id` = '${getRandomString()}' where `id` = '$id'")
}
}
}
})
.build()
}
@Provides
@Singleton
fun getSharedPre(@ApplicationContext context: Context): SharedPreferences{
return MySharedPreferences(context).sharedPref
}
}

View File

@@ -9,9 +9,11 @@ import androidx.core.content.ContextCompat
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.databinding.ActivityAuthenticationBinding
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

View File

@@ -1,10 +1,7 @@
package com.yogeshpaliyal.keypass.ui.backup
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
@@ -21,9 +18,12 @@ import com.yogeshpaliyal.keypass.databinding.BackupActivityBinding
import com.yogeshpaliyal.keypass.databinding.LayoutBackupKeypharseBinding
import com.yogeshpaliyal.keypass.db_helper.createBackup
import com.yogeshpaliyal.keypass.utils.*
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import java.net.URLDecoder
import javax.inject.Inject
@AndroidEntryPoint
class BackupActivity : AppCompatActivity() {
companion object {
@@ -54,8 +54,15 @@ class BackupActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var sp : SharedPreferences
@Inject
lateinit var appDb : AppDatabase
private val CHOOSE_BACKUPS_LOCATION_REQUEST_CODE = 26212
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -70,8 +77,8 @@ class BackupActivity : AppCompatActivity() {
}
"create_backup" -> {
context?.let {
if (it.canUserAccessBackupDirectory()) {
val selectedDirectory = Uri.parse(getBackupDirectory())
if (it.canUserAccessBackupDirectory(sp)) {
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
backup(selectedDirectory)
}
}
@@ -90,14 +97,14 @@ class BackupActivity : AppCompatActivity() {
}
private fun updateItems() {
val isBackupEnabled = context?.canUserAccessBackupDirectory() ?: false
val isBackupEnabled = context?.canUserAccessBackupDirectory(sp) ?: false
findPreference<Preference>("start_backup")?.isVisible = isBackupEnabled.not()
findPreference<Preference>("create_backup")?.isVisible = isBackupEnabled
findPreference<Preference>("create_backup")?.summary = "Last backup : ${getBackupTime().formatCalendar("dd MMM yyyy hh:mm aa")}"
findPreference<Preference>("create_backup")?.summary = "Last backup : ${getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa")}"
findPreference<Preference>("backup_folder")?.isVisible = isBackupEnabled
val directory = URLDecoder.decode(getBackupDirectory(),"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
@@ -131,7 +138,7 @@ class BackupActivity : AppCompatActivity() {
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
setBackupDirectory(selectedDirectory.toString())
setBackupDirectory(sp,selectedDirectory.toString())
backup(selectedDirectory)
}
}
@@ -139,7 +146,7 @@ class BackupActivity : AppCompatActivity() {
private fun backup(selectedDirectory: Uri){
val keyPair = getOrCreateBackupKey()
val keyPair = getOrCreateBackupKey(sp)
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
"*/*",
@@ -148,14 +155,14 @@ class BackupActivity : AppCompatActivity() {
lifecycleScope.launch {
context?.contentResolver?.let {
AppDatabase.getInstance().createBackup(keyPair.second,
appDb.createBackup(keyPair.second,
it,
tempFile?.uri
)
setBackupTime(System.currentTimeMillis())
setBackupTime(sp,System.currentTimeMillis())
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey().second
binding.txtCode.text = getOrCreateBackupKey(sp).second
binding.txtCode.setOnClickListener {
val clipboard =
ContextCompat.getSystemService(
@@ -189,9 +196,9 @@ class BackupActivity : AppCompatActivity() {
}
private fun stopBackup(){
clearBackupKey()
setBackupDirectory("")
setBackupTime(-1)
clearBackupKey(sp)
setBackupDirectory(sp,"")
setBackupTime(sp,-1)
updateItems()
}

View File

@@ -5,46 +5,50 @@ 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
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.databinding.FragmentDetailBinding
import com.yogeshpaliyal.keypass.utils.PasswordGenerator
import com.yogeshpaliyal.keypass.utils.initViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* yogeshpaliyal.foss@gmail.com
* https://techpaliyal.com
* created on 31-01-2021 10:38
*/
@AndroidEntryPoint
class DetailActivity : AppCompatActivity() {
lateinit var binding : FragmentDetailBinding
lateinit var binding: FragmentDetailBinding
companion object{
@Inject
lateinit var appDb: AppDatabase
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)
.putExtra(ARG_ACCOUNT_ID, accountId)
context?.startActivity(starter)
}
}
private val mViewModel by lazy {
initViewModel(DetailViewModel::class.java)
}
private val mViewModel by viewModels<DetailViewModel>()
private val accountId by lazy {
@@ -66,7 +70,7 @@ class DetailActivity : AppCompatActivity() {
binding.bottomAppBar.replaceMenu(R.menu.bottom_app_bar_detail)
binding.tilPassword.startIconDrawable = null
}else{
} else {
binding.tilPassword.setStartIconDrawable(R.drawable.ic_round_refresh_24)
binding.tilPassword.setStartIconOnClickListener {
@@ -80,7 +84,7 @@ class DetailActivity : AppCompatActivity() {
onBackPressed()
}
binding.bottomAppBar.setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_delete){
if (item.itemId == R.id.action_delete) {
deleteAccount()
return@setOnMenuItemClickListener true
}
@@ -93,7 +97,7 @@ class DetailActivity : AppCompatActivity() {
lifecycleScope.launch(Dispatchers.IO) {
val model = mViewModel.accountModel.value
if (model != null) {
AppDatabase.getInstance().getDao().insertOrUpdateAccount(model)
appDb.getDao().insertOrUpdateAccount(model)
}
withContext(Dispatchers.Main) {
onBackPressed()
@@ -106,19 +110,20 @@ class DetailActivity : AppCompatActivity() {
MaterialAlertDialogBuilder(this)
.setTitle("Are you sure?")
.setMessage("Do you really want to delete this entry, it can't be restored")
.setPositiveButton("Delete"
.setPositiveButton(
"Delete"
) { dialog, which ->
dialog?.dismiss()
lifecycleScope.launch(Dispatchers.IO) {
if (accountId > 0L) {
AppDatabase.getInstance().getDao().deleteAccount(accountId)
appDb.getDao().deleteAccount(accountId)
}
withContext(Dispatchers.Main) {
onBackPressed()
}
}
}
.setNegativeButton("Cancel"){dialog, which ->
.setNegativeButton("Cancel") { dialog, which ->
dialog.dismiss()
}.show()
@@ -129,8 +134,4 @@ class DetailActivity : AppCompatActivity() {
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return super.onOptionsItemSelected(item)
}
}

View File

@@ -6,8 +6,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.data.AccountModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
@@ -16,7 +18,8 @@ import kotlinx.coroutines.launch
* https://techpaliyal.com
* created on 31-01-2021 11:52
*/
class DetailViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class DetailViewModel @Inject constructor(application: Application, val appDb: AppDatabase) : AndroidViewModel(application) {
val accountModel by lazy { MutableLiveData<AccountModel>() }
@@ -24,7 +27,7 @@ class DetailViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch(Dispatchers.IO) {
accountModel.postValue(
AppDatabase.getInstance().getDao().getAccount(accountId) ?: AccountModel()
appDb.getDao().getAccount(accountId) ?: AccountModel()
)
}

View File

@@ -7,10 +7,13 @@ import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import com.yogeshpaliyal.keypass.R
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
override fun onCreate(savedInstanceState: Bundle?) {
@@ -28,14 +31,14 @@ class GeneratePasswordActivity : AppCompatActivity() {
val clipboard= getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("random_password", binding.etPassword.text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "Copied to clipboard", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
}
}
private fun generatePassword(){
val password = PasswordGenerator(
10, binding.cbCapAlphabets.isChecked,
binding.sliderPasswordLength.value.toInt(), binding.cbCapAlphabets.isChecked,
binding.cbLowerAlphabets.isChecked,
binding.cbSymbols.isChecked,
binding.cbNumbers.isChecked

View File

@@ -5,21 +5,18 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
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
import com.yogeshpaliyal.universal_adapter.utils.UniversalAdapterOptions
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -32,25 +29,30 @@ import kotlinx.coroutines.withContext
* https://techpaliyal.com
* created on 31-01-2021 09:25
*/
@AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding : FragmentHomeBinding
private lateinit var binding: FragmentHomeBinding
private val mViewModel by viewModels<HomeViewModel>()
private val mViewModel by lazy {
initViewModel(HomeViewModel::class.java)
}
private val args: HomeFragmentArgs by navArgs()
private val mAdapter by lazy {
val adapterOptions = UniversalAdapterOptions<AccountModel>(this,
content = UniversalAdapterViewType.Content(R.layout.item_accounts,mListener = mListener),
noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts))
val adapterOptions = UniversalRecyclerAdapter.Builder<AccountModel>(
this,
content = UniversalAdapterViewType.Content(
R.layout.item_accounts,
listener = mListener
),
noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts)
)
UniversalRecyclerAdapter<AccountModel>(adapterOptions)
}
val mListener = object : UniversalClickListener<AccountModel>{
val mListener = object : UniversalClickListener<AccountModel> {
override fun onItemClick(view: View, model: AccountModel) {
DetailActivity.start(context, model.id)
}
@@ -61,15 +63,15 @@ class HomeFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(layoutInflater,container,false)
binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.adapter = mAdapter
lifecycleScope.launch(Dispatchers.IO){
binding.recyclerView.adapter = mAdapter.getAdapter()
lifecycleScope.launch() {
mViewModel.loadData(args.tag).collect {
withContext(Dispatchers.Main) {
mAdapter.updateData(Resource.success(ArrayList(it)))
@@ -80,5 +82,4 @@ class HomeFragment : Fragment() {
}
}

View File

@@ -7,8 +7,10 @@ import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.data.AccountModel
import com.yogeshpaliyal.universal_adapter.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
@@ -17,14 +19,9 @@ import kotlinx.coroutines.launch
* https://techpaliyal.com
* created on 30-01-2021 23:02
*/
class HomeViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class HomeViewModel @Inject constructor(application: Application, val appDb : AppDatabase) : AndroidViewModel(application) {
private val dao by lazy {
AppDatabase.getInstance().getDao()
}
suspend fun loadData(tag : String?)= if (tag.isNullOrBlank())dao.getAllAccounts() else dao.getAllAccounts(tag)
suspend fun loadData(tag : String?)= if (tag.isNullOrBlank())appDb.getDao().getAllAccounts() else appDb.getDao().getAllAccounts(tag)
}

View File

@@ -8,7 +8,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
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
@@ -19,14 +21,17 @@ 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
/**
* A [Fragment] which acts as a bottom navigation drawer.
*/
@AndroidEntryPoint
class BottomNavDrawerFragment :
Fragment(),
NavigationAdapter.NavigationAdapterListener {
@@ -39,9 +44,8 @@ class BottomNavDrawerFragment :
from(binding.foregroundContainer)
}
private val mViewModel by lazy {
initViewModel(BottomNavViewModel::class.java)
}
private val mViewModel by viewModels<BottomNavViewModel>()
private val bottomSheetCallback = BottomNavigationDrawerCallback()

View File

@@ -6,8 +6,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.keypass.AppDatabase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
/*
@@ -16,9 +18,10 @@ import kotlinx.coroutines.launch
* https://techpaliyal.com
* created on 31-01-2021 14:11
*/
class BottomNavViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class BottomNavViewModel @Inject constructor (application: Application,val appDb: AppDatabase) : AndroidViewModel(application) {
private val _navigationList: MutableLiveData<List<NavigationModelItem>> = MutableLiveData()
private val tagsDb = AppDatabase.getInstance().getDao().getTags()
private val tagsDb = appDb.getDao().getTags()
private var tagsList : List<String> ?= null

View File

@@ -24,10 +24,12 @@ import com.yogeshpaliyal.keypass.ui.detail.DetailActivity
import com.yogeshpaliyal.keypass.ui.generate.GeneratePasswordActivity
import com.yogeshpaliyal.keypass.ui.home.HomeFragmentDirections
import com.yogeshpaliyal.keypass.ui.settings.MySettingsFragmentDirections
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@AndroidEntryPoint
class DashboardActivity : AppCompatActivity(),
Toolbar.OnMenuItemClickListener,
NavController.OnDestinationChangedListener,

View File

@@ -2,10 +2,7 @@ package com.yogeshpaliyal.keypass.ui.settings
import android.R.attr.label
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
@@ -24,13 +21,22 @@ import com.yogeshpaliyal.keypass.db_helper.createBackup
import com.yogeshpaliyal.keypass.db_helper.restoreBackup
import com.yogeshpaliyal.keypass.ui.backup.BackupActivity
import com.yogeshpaliyal.keypass.utils.*
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
@Inject
lateinit var sp : SharedPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
@@ -68,12 +74,13 @@ class MySettingsFragment : PreferenceFragmentCompat() {
}
private fun selectBackupDirectory(){
val selectedDirectory = Uri.parse(getBackupDirectory())
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
context?.let {
if(it.canUserAccessBackupDirectory()){
if(it.canUserAccessBackupDirectory(sp)){
backup(selectedDirectory)
}else{
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(
@@ -121,7 +128,7 @@ class MySettingsFragment : PreferenceFragmentCompat() {
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
setBackupDirectory(selectedDirectory.toString())
setBackupDirectory(sp,selectedDirectory.toString())
backup(selectedDirectory)
}
} else if (requestCode == CHOOSE_RESTORE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK){
@@ -141,7 +148,7 @@ class MySettingsFragment : PreferenceFragmentCompat() {
"Restore"
) { dialog, which ->
lifecycleScope.launch {
val result = AppDatabase.getInstance().restoreBackup(binding.etKeyPhrase.text.toString(),contentResolver, selectedFile)
val result = appDb.restoreBackup(binding.etKeyPhrase.text.toString(),contentResolver, selectedFile)
if (result) {
dialog?.dismiss()
Toast.makeText(
@@ -166,7 +173,7 @@ class MySettingsFragment : PreferenceFragmentCompat() {
fun backup(selectedDirectory: Uri){
val keyPair = getOrCreateBackupKey()
val keyPair = getOrCreateBackupKey(sp)
val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
"*/*",
@@ -175,19 +182,19 @@ class MySettingsFragment : PreferenceFragmentCompat() {
lifecycleScope.launch {
context?.contentResolver?.let {
AppDatabase.getInstance().createBackup(keyPair.second,
appDb.createBackup(keyPair.second,
it,
tempFile?.uri
)
if (keyPair.first) {
val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
binding.txtCode.text = getOrCreateBackupKey().second
binding.txtCode.text = getOrCreateBackupKey(sp).second
binding.txtCode.setOnClickListener {
val clipboard =
getSystemService(requireContext(), ClipboardManager::class.java)
val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
val clip = ClipData.newPlainText(getString(R.string.app_name), binding.txtCode.text)
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
}
MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
.setPositiveButton(

View File

@@ -1,6 +1,7 @@
package com.yogeshpaliyal.keypass.utils
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
@@ -28,8 +29,8 @@ fun getRandomString(sizeOfRandomString: Int): String {
}
fun Context.canUserAccessBackupDirectory(): Boolean {
val backupDirectoryUri = getUri(getBackupDirectory()) ?: return false
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()
}

View File

@@ -0,0 +1,38 @@
package com.yogeshpaliyal.keypass.utils
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
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 {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
// this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC
// this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC
val spec = KeyGenParameterSpec.Builder(
MasterKey.DEFAULT_MASTER_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
it.setKeyGenParameterSpec(spec)
}
//it.setUserAuthenticationRequired(true)
}
.build()
val sharedPref = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

View File

@@ -16,43 +16,13 @@ import com.yogeshpaliyal.keypass.MyApplication
* created on 21-02-2021 11:18
*/
fun getSharedPreferences(): SharedPreferences {
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
// this is equivalent to using deprecated MasterKeys.AES256_GCM_SPEC
val spec = KeyGenParameterSpec.Builder(
MasterKey.DEFAULT_MASTER_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
it.setKeyGenParameterSpec(spec)
}
//it.setUserAuthenticationRequired(true)
}
.build()
return EncryptedSharedPreferences.create(
MyApplication.instance,
"secret_shared_prefs",
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
/**
* Pair
* 1st => true if key is created now & false if key is created previously
*
*/
fun getOrCreateBackupKey(reset: Boolean = false): Pair<Boolean, String> {
val sp = getSharedPreferences()
fun getOrCreateBackupKey(sp: SharedPreferences,reset: Boolean = false): Pair<Boolean, String> {
return if (sp.contains(BACKUP_KEY) && reset.not()) {
Pair(false, sp.getString(BACKUP_KEY, "") ?: "")
@@ -65,8 +35,8 @@ fun getOrCreateBackupKey(reset: Boolean = false): Pair<Boolean, String> {
}
}
fun clearBackupKey() {
val sp = getSharedPreferences()
fun clearBackupKey(sp: SharedPreferences) {
sp.edit {
remove(BACKUP_KEY)
}
@@ -74,25 +44,23 @@ fun clearBackupKey() {
}
fun setBackupDirectory(string: String) {
getSharedPreferences().edit {
fun setBackupDirectory(sp: SharedPreferences,string: String) {
sp.edit {
putString(BACKUP_DIRECTORY, string)
}
}
fun setBackupTime(time: Long) {
getSharedPreferences().edit {
fun setBackupTime(sp: SharedPreferences,time: Long) {
sp.edit {
putLong(BACKUP_DATE_TIME, time)
}
}
fun getBackupDirectory(): String {
val sp = getSharedPreferences()
fun getBackupDirectory(sp: SharedPreferences,): String {
return sp.getString(BACKUP_DIRECTORY, "") ?: ""
}
fun getBackupTime(): Long {
val sp = getSharedPreferences()
fun getBackupTime(sp: SharedPreferences,): Long {
return sp.getLong(BACKUP_DATE_TIME, -1) ?: -1L
}

View File

@@ -30,7 +30,7 @@
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:hint="@string/password"
app:endIconDrawable="@drawable/ic_twotone_content_copy_24"
app:endIconMode="custom"
app:layout_constraintEnd_toEndOf="@id/endGuideline"
@@ -44,34 +44,49 @@
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="UPPERCASE ALPHABETS" />
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="lowercase alphabets" />
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="Numbers" />
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="Symbols" />
android:text="@string/symbols" />
</LinearLayout>
</com.yogeshpaliyal.keypass.custom_views.MaskedCardView>

View File

@@ -37,4 +37,11 @@
<string name="send_feedback_desc">Report technical issues or suggest new features</string>
<string name="share">Share</string>
<string name="share_desc">Share app with others</string>
<string name="password_length">Password length</string>
<string name="password">Password</string>
<string name="uppercase_alphabets">UPPERCASE ALPHABETS</string>
<string name="lowercase_alphabets">lowercase alphabets</string>
<string name="symbols">Symbols</string>
<string name="numbers">Numbers</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
</resources>

View File

@@ -3,20 +3,25 @@ buildscript {
ext{
kotlin_version = "1.4.21"
lifecycle_version = "2.2.0"
room_version = "2.2.6"
navigation_version = '2.3.2'
lifecycle_version = "2.3.1"
room_version = "2.3.0"
navigation_version = '2.3.5'
ext.hilt_version = '2.35'
}
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.2"
classpath "com.android.tools.build:gradle:4.1.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.gms:google-services:4.3.8'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

View File

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