diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 3c7772a0..7643783a 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,22 +1,6 @@ - - diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 61a9130c..fb7f4a8a 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 22ffc66c..c6e0331b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,10 +4,10 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index d5d35ec4..ef61796f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index be4adcdc..1abe13ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt b/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt index b2bc2ed2..f8045cef 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/AppDatabase.kt @@ -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 - } - } - - } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt b/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt index a8606431..3fc8fc51 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt @@ -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{ diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt index 075f99f8..fcb2c051 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/db_helper/DbBackupRestore.kt @@ -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() } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/di/module/AppModule.kt b/app/src/main/java/com/yogeshpaliyal/keypass/di/module/AppModule.kt new file mode 100644 index 00000000..1c101f1b --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/di/module/AppModule.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt index 71108dc5..d7c3da91 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthenticationActivity.kt @@ -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 diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt index dba22817..72573ff0 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt @@ -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("start_backup")?.isVisible = isBackupEnabled.not() findPreference("create_backup")?.isVisible = isBackupEnabled - findPreference("create_backup")?.summary = "Last backup : ${getBackupTime().formatCalendar("dd MMM yyyy hh:mm aa")}" + findPreference("create_backup")?.summary = "Last backup : ${getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa")}" findPreference("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("backup_folder")?.summary = folderName findPreference("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() } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt index d6ea15de..82db216d 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt @@ -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() 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) - } } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt index 9121eabe..08eeb9b5 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt @@ -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() } @@ -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() ) } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordActivity.kt index d50b1081..51be98eb 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordActivity.kt @@ -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 diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt index 89323161..93f97bc8 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeFragment.kt @@ -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() - private val mViewModel by lazy { - initViewModel(HomeViewModel::class.java) - } private val args: HomeFragmentArgs by navArgs() private val mAdapter by lazy { - val adapterOptions = UniversalAdapterOptions(this, - content = UniversalAdapterViewType.Content(R.layout.item_accounts,mListener = mListener), - noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts)) + val adapterOptions = UniversalRecyclerAdapter.Builder( + this, + content = UniversalAdapterViewType.Content( + R.layout.item_accounts, + listener = mListener + ), + noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts) + ) UniversalRecyclerAdapter(adapterOptions) } - val mListener = object : UniversalClickListener{ + val mListener = object : UniversalClickListener { 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() { } - } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeViewModel.kt index 08be6540..50868aa1 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/HomeViewModel.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavDrawerFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavDrawerFragment.kt index 4dae6b8b..6a42f175 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavDrawerFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavDrawerFragment.kt @@ -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() + private val bottomSheetCallback = BottomNavigationDrawerCallback() diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavViewModel.kt index 7d5f026e..6c1deefa 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/BottomNavViewModel.kt @@ -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> = MutableLiveData() - private val tagsDb = AppDatabase.getInstance().getDao().getTags() + private val tagsDb = appDb.getDao().getTags() private var tagsList : List ?= null diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardActivity.kt index c2900a20..ac41c72f 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardActivity.kt @@ -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, diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt index 4b8624ee..c9595c71 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt @@ -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( diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt index 269785b9..bc1803a5 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt @@ -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() } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/MySharedPreferences.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/MySharedPreferences.kt new file mode 100644 index 00000000..fa693ee4 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/MySharedPreferences.kt @@ -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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt index 8621681d..6d89c305 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt @@ -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 { - val sp = getSharedPreferences() +fun getOrCreateBackupKey(sp: SharedPreferences,reset: Boolean = false): Pair { return if (sp.contains(BACKUP_KEY) && reset.not()) { Pair(false, sp.getString(BACKUP_KEY, "") ?: "") @@ -65,8 +35,8 @@ fun getOrCreateBackupKey(reset: Boolean = false): Pair { } } -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 } diff --git a/app/src/main/res/layout/activity_generate_password.xml b/app/src/main/res/layout/activity_generate_password.xml index 5af922df..327fb344 100644 --- a/app/src/main/res/layout/activity_generate_password.xml +++ b/app/src/main/res/layout/activity_generate_password.xml @@ -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" /> + + + + + + + android:text="@string/uppercase_alphabets" /> + android:text="@string/lowercase_alphabets" /> + android:text="@string/numbers" /> + android:text="@string/symbols" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0787ef6a..6022f7c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,4 +37,11 @@ Report technical issues or suggest new features Share Share app with others + Password length + Password + UPPERCASE ALPHABETS + lowercase alphabets + Symbols + Numbers + Copied to clipboard \ No newline at end of file diff --git a/build.gradle b/build.gradle index b091cc56..1fa2dfd1 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7d4ad505..168304f9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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