From 8b0df00edcf8a05cbb2d724367594067068ad0c7 Mon Sep 17 00:00:00 2001 From: hexCode63 Date: Thu, 2 Oct 2025 17:48:32 +0100 Subject: [PATCH 01/15] Updated: - Ability to set as default password management application - Added local database storage to store, save and recall user saved password information --- app/src/main/AndroidManifest.xml | 15 +++ .../autofill/AutofillSettingsActivity.kt | 23 ++++ .../autofill/PasswordAutofillService.kt | 112 ++++++++++++++++++ .../jeeldobariya/passcodes/data/Passcode.kt | 12 ++ .../passcodes/data/PasscodeDao.kt | 28 +++++ .../passcodes/data/PasscodeDatabase.kt | 29 +++++ .../res/layout/activity_autofill_settings.xml | 29 +++++ app/src/main/res/layout/activity_main.xml | 10 +- .../main/res/layout/autofill_list_item.xml | 14 +++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/autofill_service.xml | 4 + gradle/libs.versions.toml | 2 +- 12 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/data/Passcode.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDao.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDatabase.kt create mode 100644 app/src/main/res/layout/activity_autofill_settings.xml create mode 100644 app/src/main/res/layout/autofill_list_item.xml create mode 100644 app/src/main/res/xml/autofill_service.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 21615d35..93fbdc42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,7 @@ + + + + + + + + + diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt new file mode 100644 index 00000000..13472f86 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt @@ -0,0 +1,23 @@ +package com.jeeldobariya.passcodes.autofill + +import android.content.Intent +import android.os.Bundle +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import com.jeeldobariya.passcodes.R +import com.google.android.material.button.MaterialButton + +class AutofillSettingsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_autofill_settings) + + val enableAutofillButton = findViewById(R.id.enable_autofill_button) + enableAutofillButton.setOnClickListener { + val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) + intent.data = android.net.Uri.parse("package:$packageName") + startActivity(intent) + } + } +} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt new file mode 100644 index 00000000..32845d8c --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt @@ -0,0 +1,112 @@ +package com.jeeldobariya.passcodes.autofill + +import android.app.assist.AssistStructure +import android.os.CancellationSignal +import android.service.autofill.AutofillService +import android.service.autofill.Dataset +import android.service.autofill.FillCallback +import android.service.autofill.FillContext +import android.service.autofill.FillRequest +import android.service.autofill.FillResponse +import android.service.autofill.SaveCallback +import android.service.autofill.SaveRequest +import android.view.autofill.AutofillValue +import android.widget.RemoteViews +import com.jeeldobariya.passcodes.R +import com.jeeldobariya.passcodes.data.Passcode +import com.jeeldobariya.passcodes.data.PasscodeDatabase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class PasswordAutofillService : AutofillService() { + + private val serviceScope = CoroutineScope(Dispatchers.IO) + + override fun onFillRequest( + request: FillRequest, + cancellationSignal: CancellationSignal, + callback: FillCallback + ) { + val context = request.fillContexts + val structure = context.last().structure + + val viewNodes = mutableMapOf() + parseStructure(structure.getWindowNodeAt(0).rootViewNode, viewNodes) + + val usernameNode = viewNodes["username"] ?: viewNodes["emailAddress"] + val passwordNode = viewNodes["password"] + + if (usernameNode?.autofillId == null || passwordNode?.autofillId == null) { + callback.onSuccess(null) + return + } + + val usernameId = usernameNode.autofillId!! + val passwordId = passwordNode.autofillId!! + + cancellationSignal.setOnCancelListener { + // Handle cancellation + } + + serviceScope.launch { + val passcodes = PasscodeDatabase.getDatabase(applicationContext).passcodeDao().getAllPasscodes().first() + val responseBuilder = FillResponse.Builder() + + for (passcode in passcodes) { + val presentation = RemoteViews(packageName, R.layout.autofill_list_item).apply { + setTextViewText(R.id.autofill_username, passcode.name) + } + + val dataset = android.service.autofill.Dataset.Builder(presentation) + .setValue(usernameId, AutofillValue.forText(passcode.name)) + .setValue(passwordId, AutofillValue.forText(passcode.value)) + .build() + responseBuilder.addDataset(dataset) + } + + callback.onSuccess(responseBuilder.build()) + } + } + + override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { + val context = request.fillContexts + val structure = context.last().structure + + val viewNodes = mutableMapOf() + parseStructure(structure.getWindowNodeAt(0).rootViewNode, viewNodes) + + val usernameNode = viewNodes["username"] ?: viewNodes["emailAddress"] + val passwordNode = viewNodes["password"] + + val username = usernameNode?.text?.toString() + val password = passwordNode?.text?.toString() + + if (!username.isNullOrEmpty() && !password.isNullOrEmpty()) { + serviceScope.launch { + PasscodeDatabase.getDatabase(applicationContext).passcodeDao().insert( + Passcode(name = username, value = password) + ) + } + callback.onSuccess() + } else { + callback.onFailure("Could not save credentials.") + } + } + + private fun parseStructure( + node: AssistStructure.ViewNode, + viewNodes: MutableMap + ) { + node.autofillHints?.forEach { hint -> + if (!viewNodes.containsKey(hint)) { + viewNodes[hint] = node + } + } + + for (i in 0 until node.childCount) { + parseStructure(node.getChildAt(i), viewNodes) + } + } +} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/data/Passcode.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/Passcode.kt new file mode 100644 index 00000000..e175e96f --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/Passcode.kt @@ -0,0 +1,12 @@ +package com.jeeldobariya.passcodes.data + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "passcodes") +data class Passcode( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val name: String, + val value: String +) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDao.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDao.kt new file mode 100644 index 00000000..957d5a37 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDao.kt @@ -0,0 +1,28 @@ +package com.jeeldobariya.passcodes.data + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow + +@Dao +interface PasscodeDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(passcode: Passcode) + + @Update + suspend fun update(passcode: Passcode) + + @Delete + suspend fun delete(passcode: Passcode) + + @Query("SELECT * FROM passcodes ORDER BY name ASC") + fun getAllPasscodes(): Flow> + + @Query("SELECT * FROM passcodes WHERE id = :id") + fun getPasscode(id: Int): Flow +} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDatabase.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDatabase.kt new file mode 100644 index 00000000..1fe03c95 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDatabase.kt @@ -0,0 +1,29 @@ +package com.jeeldobariya.passcodes.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [Passcode::class], version = 1, exportSchema = false) +abstract class PasscodeDatabase : RoomDatabase() { + + abstract fun passcodeDao(): PasscodeDao + + companion object { + @Volatile + private var INSTANCE: PasscodeDatabase? = null + + fun getDatabase(context: Context): PasscodeDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + PasscodeDatabase::class.java, + "passcode_database" + ).build() + INSTANCE = instance + instance + } + } + } +} diff --git a/app/src/main/res/layout/activity_autofill_settings.xml b/app/src/main/res/layout/activity_autofill_settings.xml new file mode 100644 index 00000000..1e1fb8db --- /dev/null +++ b/app/src/main/res/layout/activity_autofill_settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1f595077..d4d0aac0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,7 +11,7 @@ android:layout_width="128dp" android:layout_height="128dp" android:background="@drawable/ic_passodes" - android:contentDescription="Passcodes Logo" /> + android:contentDescription="@string/passcodes_logo" /> - - - - + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afd8f58b..88a3ab1e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,4 +98,5 @@ Copying sensitive data like passwords to clipboard is not so good for security!!! Confirm Discard + Passcodes Logo diff --git a/app/src/main/res/xml/autofill_service.xml b/app/src/main/res/xml/autofill_service.xml new file mode 100644 index 00000000..eb9e48c4 --- /dev/null +++ b/app/src/main/res/xml/autofill_service.xml @@ -0,0 +1,4 @@ + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94c75d95..d7fcb303 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ coroutines = "1.10.2" lifecycle = "2.9.2" # Plugin versions -agp = "8.11.0" +agp = "8.13.0" kotlin-plugin = "2.1.21" ksp = "2.1.21-2.0.2" oss-license-plugin = "0.10.6" # latest safe version for oss-licenses-plugin From a5ba130eb90eb1570d151ab637febff73321761d Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sat, 4 Oct 2025 10:57:06 +0530 Subject: [PATCH 02/15] chore: grant execute permission to gradlew --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 6c68559e1f0644d50f96c57581b796214cb68bb4 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 18:41:26 +0530 Subject: [PATCH 03/15] chore(deps): add koin & viewmodel as dependency --- app/build.gradle.kts | 5 ++++- gradle/libs.versions.toml | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eb7cd94a..f9a6e71d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -158,7 +158,10 @@ dependencies { implementation(libs.coroutines.android) implementation(libs.lifecycle.runtime) - // implementation(libs.lifecycle.viewmodel) + implementation(libs.lifecycle.viewmodel) + + implementation(libs.koin) + implementation(libs.koin.viewmodel) // test testImplementation(libs.junit) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7fcb303..211749a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ androidx-test-ext-junit = "1.2.1" espresso-core = "3.6.1" coroutines = "1.10.2" lifecycle = "2.9.2" +koin = "4.1.1" # Plugin versions agp = "8.13.0" @@ -37,7 +38,11 @@ coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-androi coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } -# lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } +lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } + +koin = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-viewmodel = { module = "io.insert-koin:koin-android", version.ref = "koin" } +# koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } json = { module = "org.json:json", version.ref = "json" } From 20b7f62b2983df9434b939b8f5bbf3abaefbafda Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 18:42:31 +0530 Subject: [PATCH 04/15] feat: create a UpdatePasswordViewModel --- .../passcodes/ui/UpdatePasswordViewmodel.kt | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt new file mode 100644 index 00000000..aece12c7 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt @@ -0,0 +1,93 @@ +package com.jeeldobariya.passcodes.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jeeldobariya.passcodes.database.Password +import com.jeeldobariya.passcodes.utils.Controller +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class UpdatePasswordViewModel( + val controller: Controller +) : ViewModel() { + + var passwordEntityId: Int = -1 + + private val _domainState = MutableStateFlow("") + val domainState = _domainState.asStateFlow() + + private val _usernameState = MutableStateFlow("") + val usernameState = _usernameState.asStateFlow() + + private val _passwordState = MutableStateFlow("") + val passwordState = _passwordState.asStateFlow() + + private val _notesState = MutableStateFlow("") + val notesState = _notesState.asStateFlow() + + private val _isErrorState = MutableStateFlow(false) + val isErrorState = _isErrorState.asStateFlow() + + fun loadInitialData(passwordId: Int) { + passwordEntityId = passwordId + + viewModelScope.launch { + try { + val password: Password = controller.getPasswordById(passwordId) + + _domainState.update { password.domain } + _usernameState.update { password.username } + _passwordState.update { password.password } + _notesState.update { password.notes } + } catch (e: Exception) { + _isErrorState.update { + true + } + } + } + } + + fun onChangeDomainText(text: String) { + _domainState.update { + text + } + } + + fun onChangeUsernameText(text: String) { + _usernameState.update { + text + } + } + + fun onChangePasswordText(text: String) { + _passwordState.update { + text + } + } + + fun onChangeNotesText(text: String) { + _notesState.update { + text + } + } + + fun onUpdatePasswordButtonClick() { + viewModelScope.launch { + try { + val rowsAffected = controller.updatePassword( + passwordEntityId, + _domainState.value, + _usernameState.value, + _passwordState.value, + _notesState.value + ) + } catch (e: Exception) { + _isErrorState.update { + true + } + } + } + } +} From 6c30299809ee6218e54e224334441507129f6019 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 18:43:27 +0530 Subject: [PATCH 05/15] feat: implement dependency injection with koin --- app/src/main/AndroidManifest.xml | 1 + .../passcodes/PasscodesApplication.kt | 16 ++++++++++++++++ .../jeeldobariya/passcodes/di/appModule.kt | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/PasscodesApplication.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 93fbdc42..2c896861 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ Date: Tue, 7 Oct 2025 18:44:10 +0530 Subject: [PATCH 06/15] feat: use viewmodel in updatepasswordactivity --- .../passcodes/ui/UpdatePasswordActivity.kt | 126 +++++------------- 1 file changed, 37 insertions(+), 89 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index 8f0f7288..95beefe3 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -1,42 +1,40 @@ package com.jeeldobariya.passcodes.ui -import android.content.Context -import android.content.Intent import android.os.Bundle import android.widget.Toast import android.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.jeeldobariya.passcodes.R -import com.jeeldobariya.passcodes.database.Password import com.jeeldobariya.passcodes.databinding.ActivityUpdatePasswordBinding import com.jeeldobariya.passcodes.utils.CommonUtils -import com.jeeldobariya.passcodes.utils.Controller -import com.jeeldobariya.passcodes.utils.DatabaseOperationException -import com.jeeldobariya.passcodes.utils.InvalidInputException -import com.jeeldobariya.passcodes.utils.PasswordNotFoundException -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf /* * Activity expects id as intent parameters. */ class UpdatePasswordActivity : AppCompatActivity() { - private var passwordEntityId: Int = 0 - private lateinit var controller: Controller + private val viewModel: UpdatePasswordViewModel by viewModel() + private lateinit var binding: ActivityUpdatePasswordBinding override fun onCreate(savedInstanceState: Bundle?) { CommonUtils.updateCurrTheme(this) + super.onCreate(savedInstanceState) binding = ActivityUpdatePasswordBinding.inflate(layoutInflater) setContentView(binding.root) val intent = intent - passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. + val passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. if (passwordEntityId == -1) { // invalid entity Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT).show() @@ -44,10 +42,22 @@ class UpdatePasswordActivity : AppCompatActivity() { return // Exit onCreate if ID is invalid } - controller = Controller(this) + viewModel.loadInitialData(passwordEntityId) - // Fill data into views - fillDataInViews() // Call this to populate initial data + binding.tvId.setText("${getString(R.string.id_prefix)} ${viewModel.passwordEntityId}") + + collectLatestLifecycleFlow(viewModel.domainState) { domain -> + binding.inputDomain.setText(domain) + } + collectLatestLifecycleFlow(viewModel.usernameState) { username -> + binding.inputUsername.setText(username) + } + collectLatestLifecycleFlow(viewModel.passwordState) { password -> + binding.inputPassword.setText(password) + } + collectLatestLifecycleFlow(viewModel.notesState) { notes -> + binding.inputNotes.setText(notes) + } // Add event onclick listener addOnClickListenerOnButton() @@ -56,96 +66,34 @@ class UpdatePasswordActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) } - private fun fillDataInViews() { - lifecycleScope.launch { - try { - val passwordEntity: Password = controller.getPasswordById(passwordEntityId) - withContext(Dispatchers.Main) { - binding.tvId.text = "${getString(R.string.id_prefix)} $passwordEntityId" - binding.inputDomain.setText(passwordEntity.domain) - binding.inputUsername.setText(passwordEntity.username) - binding.inputPassword.setText(passwordEntity.password) - binding.inputNotes.setText(passwordEntity.notes) - } - } catch (e: PasswordNotFoundException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, e.message, Toast.LENGTH_LONG).show() - e.printStackTrace() - finish() - } - } catch (e: DatabaseOperationException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() - e.printStackTrace() - finish() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() - e.printStackTrace() - finish() - } - } - } - } - // Added all the onclick event listeners private fun addOnClickListenerOnButton() { binding.updatePasswordBtn.setOnClickListener { - val newDomain = binding.inputDomain.text.toString() - val newUsername = binding.inputUsername.text.toString() - val newPassword = binding.inputPassword.text.toString() - val newNotes = binding.inputNotes.text.toString() + viewModel.onChangeDomainText(binding.inputDomain.text.toString()) + viewModel.onChangeUsernameText(binding.inputUsername.text.toString()) + viewModel.onChangePasswordText(binding.inputPassword.text.toString()) + viewModel.onChangeNotesText(binding.inputNotes.text.toString()) val confirmDialog = AlertDialog.Builder(this@UpdatePasswordActivity) .setTitle(R.string.update_password_dialog_title) .setMessage(R.string.irreversible_dialog_desc) .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> - performUpdatePasswordAction(newDomain, newUsername, newPassword, newNotes); + viewModel.onUpdatePasswordButtonClick() } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT).show(); } - .create(); - + .create() + confirmDialog.show(); } } +} - fun performUpdatePasswordAction(newDomain: String, newUsername: String, newPassword: String, newNotes: String) { - lifecycleScope.launch { - try { - val rowsAffected = controller.updatePassword(passwordEntityId, newDomain, newUsername, newPassword, newNotes) - withContext(Dispatchers.Main) { - if (rowsAffected > 0) { - Toast.makeText(this@UpdatePasswordActivity, getString(R.string.update_success_msg), Toast.LENGTH_SHORT).show() - finish() - } else { - Toast.makeText(this@UpdatePasswordActivity, getString(R.string.something_went_wrong_msg) + ": No changes applied or password not found.", Toast.LENGTH_SHORT).show() - finish() - } - } - } catch (e: InvalidInputException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, getString(R.string.warn_fill_form), Toast.LENGTH_SHORT).show() - } - } catch (e: PasswordNotFoundException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, e.message, Toast.LENGTH_LONG).show() - e.printStackTrace() - finish() - } - } catch (e: DatabaseOperationException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, "${getString(R.string.fail_msg)}: ${e.message}", Toast.LENGTH_LONG).show() - e.printStackTrace() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText(this@UpdatePasswordActivity, "${getString(R.string.fail_msg)}: ${e.message}", Toast.LENGTH_LONG).show() - e.printStackTrace() - } - } +fun AppCompatActivity.collectLatestLifecycleFlow(flow: Flow, collect: (T) -> Unit) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + flow.collectLatest(collect) } } } From a2ba5c2853294816d31942e19493b48ff7feb46d Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 18:46:45 +0530 Subject: [PATCH 07/15] refactor: structure of code --- .../passcodes/ui/UpdatePasswordActivity.kt | 8 -------- ...dViewmodel.kt => UpdatePasswordViewModel.kt} | 0 .../passcodes/utils/tempmigration.kt | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) rename app/src/main/kotlin/com/jeeldobariya/passcodes/ui/{UpdatePasswordViewmodel.kt => UpdatePasswordViewModel.kt} (100%) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/utils/tempmigration.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index 95beefe3..3a2f1bfb 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -89,11 +89,3 @@ class UpdatePasswordActivity : AppCompatActivity() { } } } - -fun AppCompatActivity.collectLatestLifecycleFlow(flow: Flow, collect: (T) -> Unit) { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - flow.collectLatest(collect) - } - } -} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt similarity index 100% rename from app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewmodel.kt rename to app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/tempmigration.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/tempmigration.kt new file mode 100644 index 00000000..284e494b --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/tempmigration.kt @@ -0,0 +1,17 @@ +package com.jeeldobariya.passcodes.utils + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +fun AppCompatActivity.collectLatestLifecycleFlow(flow: Flow, collect: (T) -> Unit) { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + flow.collectLatest(collect) + } + } +} From 45358e29797a82e839335d5db8508bedc4a15e08 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 18:58:12 +0530 Subject: [PATCH 08/15] fix: error after refactor (collectLatestLifecycleFlow) --- .../jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index 3a2f1bfb..314bdec1 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -5,17 +5,12 @@ import android.widget.Toast import android.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.databinding.ActivityUpdatePasswordBinding import com.jeeldobariya.passcodes.utils.CommonUtils -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch +import com.jeeldobariya.passcodes.utils.collectLatestLifecycleFlow import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf + /* * Activity expects id as intent parameters. From bc35d42f19dcd8b8b96c53109145f089b6515eb9 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Tue, 7 Oct 2025 19:11:31 +0530 Subject: [PATCH 09/15] lint: formatted code --- .../passcodes/PasscodesApplication.kt | 2 +- .../autofill/PasswordAutofillService.kt | 4 +- .../passcodes/database/MasterDatabase.kt | 4 +- .../jeeldobariya/passcodes/di/appModule.kt | 2 +- .../passcodes/flags/FeatureFlagManager.kt | 10 ++- .../passcodes/ui/AboutUsActivity.kt | 64 +++++++------- .../passcodes/ui/LoadPasswordActivity.kt | 6 +- .../passcodes/ui/PasswordManagerActivity.kt | 24 ++++-- .../passcodes/ui/SavePasswordActivity.kt | 37 ++++++-- .../passcodes/ui/SettingsActivity.kt | 54 ++++++++---- .../passcodes/ui/UpdatePasswordActivity.kt | 10 ++- .../passcodes/ui/UpdatePasswordViewModel.kt | 6 +- .../passcodes/ui/ViewPasswordActivity.kt | 84 ++++++++++++++----- .../passcodes/utils/CommonUtils.kt | 3 +- .../passcodes/utils/Controller.kt | 40 +++++++-- .../passcodes/utils/Permissions.kt | 17 ++-- .../passcodes/utils/UpdateChecker.kt | 5 +- 17 files changed, 251 insertions(+), 121 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/PasscodesApplication.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/PasscodesApplication.kt index 3b4248a7..190dd4f5 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/PasscodesApplication.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/PasscodesApplication.kt @@ -5,7 +5,7 @@ import com.jeeldobariya.passcodes.di.appModule import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -class PasscodesApplication: Application() { +class PasscodesApplication : Application() { override fun onCreate() { super.onCreate() startKoin { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt index 32845d8c..2a28456e 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt @@ -51,7 +51,9 @@ class PasswordAutofillService : AutofillService() { } serviceScope.launch { - val passcodes = PasscodeDatabase.getDatabase(applicationContext).passcodeDao().getAllPasscodes().first() + val passcodes = + PasscodeDatabase.getDatabase(applicationContext).passcodeDao().getAllPasscodes() + .first() val responseBuilder = FillResponse.Builder() for (passcode in passcodes) { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt index 9adf6926..b193ddd9 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt @@ -19,13 +19,13 @@ abstract class MasterDatabase : RoomDatabase() { fun getDatabase(context: Context): MasterDatabase { return INSTANCE ?: synchronized(this) { - + val instance = Room.databaseBuilder( context.applicationContext, MasterDatabase::class.java, "master" ) - .build() + .build() INSTANCE = instance instance } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt index 07048083..b93d0a36 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt @@ -6,7 +6,7 @@ import org.koin.android.ext.koin.androidContext import org.koin.core.module.dsl.viewModel import org.koin.dsl.module -val appModule = module{ +val appModule = module { single { Controller(androidContext()) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt index 1bd2445b..b42f0a89 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt @@ -5,14 +5,18 @@ import androidx.core.content.edit import com.jeeldobariya.passcodes.utils.Constant class FeatureFlagManager private constructor(context: Context) { - private val prefs = context.getSharedPreferences(Constant.FEATURE_FLAGS_PREFS_NAME, Context.MODE_PRIVATE) + private val prefs = + context.getSharedPreferences(Constant.FEATURE_FLAGS_PREFS_NAME, Context.MODE_PRIVATE) var latestFeaturesEnabled: Boolean get() = prefs.getBoolean(Constant.LATEST_FEATURES_KEY, false) - set(value) { prefs.edit { putBoolean(Constant.LATEST_FEATURES_KEY, value) } } + set(value) { + prefs.edit { putBoolean(Constant.LATEST_FEATURES_KEY, value) } + } companion object { - @Volatile private var INSTANCE: FeatureFlagManager? = null + @Volatile + private var INSTANCE: FeatureFlagManager? = null fun get(context: Context): FeatureFlagManager { return INSTANCE ?: synchronized(this) { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt index b8824ab8..cea7a3ef 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt @@ -10,46 +10,46 @@ import com.jeeldobariya.passcodes.utils.CommonUtils class AboutUsActivity : AppCompatActivity() { - private lateinit var binding: ActivityAboutUsBinding + private lateinit var binding: ActivityAboutUsBinding - override fun onCreate(savedInstanceState: Bundle?) { - CommonUtils.updateCurrTheme(this) - super.onCreate(savedInstanceState) - binding = ActivityAboutUsBinding.inflate(layoutInflater) - setContentView(binding.root) + override fun onCreate(savedInstanceState: Bundle?) { + CommonUtils.updateCurrTheme(this) + super.onCreate(savedInstanceState) + binding = ActivityAboutUsBinding.inflate(layoutInflater) + setContentView(binding.root) - binding.toolbar.setNavigationOnClickListener { - onBackPressedDispatcher.onBackPressed() - } - - // Set up button click listeners - setupButtonListeners() - } + binding.toolbar.setNavigationOnClickListener { + onBackPressedDispatcher.onBackPressed() + } - private fun openBrowser(url: String) { - val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) - startActivity(browserIntent) - } - - private fun setupButtonListeners() { - binding.cardSecurityGuidelines.setOnClickListener { - openBrowser(Constant.SECURITY_GUIDE_URL) + // Set up button click listeners + setupButtonListeners() } - binding.cardReleaseNotes.setOnClickListener { - openBrowser(Constant.RELEASE_NOTE_URL) + private fun openBrowser(url: String) { + val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) + startActivity(browserIntent) } - binding.cardLicense.setOnClickListener { - startActivity(Intent(this, LicenseActivity::class.java)) - } + private fun setupButtonListeners() { + binding.cardSecurityGuidelines.setOnClickListener { + openBrowser(Constant.SECURITY_GUIDE_URL) + } - binding.cardReportBug.setOnClickListener { - openBrowser(Constant.REPORT_BUG_URL) - } + binding.cardReleaseNotes.setOnClickListener { + openBrowser(Constant.RELEASE_NOTE_URL) + } + + binding.cardLicense.setOnClickListener { + startActivity(Intent(this, LicenseActivity::class.java)) + } + + binding.cardReportBug.setOnClickListener { + openBrowser(Constant.REPORT_BUG_URL) + } - binding.cardTelegramCommunity.setOnClickListener { - openBrowser(Constant.TELEGRAM_COMMUNITY_URL) + binding.cardTelegramCommunity.setOnClickListener { + openBrowser(Constant.TELEGRAM_COMMUNITY_URL) + } } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt index e53687ce..917017fb 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt @@ -57,7 +57,8 @@ class LoadPasswordActivity : AppCompatActivity() { e.printStackTrace() // Ensure adapter is initialized with empty list on error if (!this@LoadPasswordActivity::passwordAdapter.isInitialized) { - passwordAdapter = PasswordAdapter(this@LoadPasswordActivity, emptyList()) + passwordAdapter = + PasswordAdapter(this@LoadPasswordActivity, emptyList()) binding.passwordList.adapter = passwordAdapter } } @@ -67,7 +68,8 @@ class LoadPasswordActivity : AppCompatActivity() { // and emitted by the Flow. withContext(Dispatchers.Main) { // Ensure UI updates are on the main thread if (!this@LoadPasswordActivity::passwordAdapter.isInitialized) { - passwordAdapter = PasswordAdapter(this@LoadPasswordActivity, passwordList) + passwordAdapter = + PasswordAdapter(this@LoadPasswordActivity, passwordList) binding.passwordList.adapter = passwordAdapter } else { passwordAdapter.updateData(passwordList) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt index 3796e70f..e31fbf77 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt @@ -49,9 +49,10 @@ class PasswordManagerActivity : AppCompatActivity() { if (result.resultCode == RESULT_OK) { val uri = result.data?.data if (uri != null) { - val CSVData: String? = contentResolver.openInputStream(uri)?.bufferedReader()?.use { - it.readText() - } + val CSVData: String? = + contentResolver.openInputStream(uri)?.bufferedReader()?.use { + it.readText() + } lifecycleScope.launch(Dispatchers.IO) { if (CSVData != null) { @@ -64,7 +65,7 @@ class PasswordManagerActivity : AppCompatActivity() { getString(R.string.import_success, result[0]), Toast.LENGTH_LONG ).show() - + if (result[1] != 0) { Toast.makeText( this@PasswordManagerActivity, @@ -97,7 +98,8 @@ class PasswordManagerActivity : AppCompatActivity() { contentResolver.openOutputStream(uri)?.use { outputStream -> outputStream.write(tmpExportCSVData!!.toByteArray()) } - Toast.makeText(this, getString(R.string.export_success), Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.export_success), Toast.LENGTH_SHORT) + .show() } } } @@ -122,13 +124,21 @@ class PasswordManagerActivity : AppCompatActivity() { } binding.importPasswordBtn.setOnClickListener { - Toast.makeText(this@PasswordManagerActivity, getString(R.string.preview_feature), Toast.LENGTH_LONG).show() + Toast.makeText( + this@PasswordManagerActivity, + getString(R.string.preview_feature), + Toast.LENGTH_LONG + ).show() importCsvFilePicker() } binding.exportPasswordBtn.setOnClickListener { - Toast.makeText(this@PasswordManagerActivity, getString(R.string.preview_feature), Toast.LENGTH_LONG).show() + Toast.makeText( + this@PasswordManagerActivity, + getString(R.string.preview_feature), + Toast.LENGTH_LONG + ).show() lifecycleScope.launch(Dispatchers.IO) { val csvDataExportBlob = controller.exportDataToCsvString() diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt index 2f2c846b..37301ee8 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt @@ -37,7 +37,7 @@ class SavePasswordActivity : AppCompatActivity() { // Added all the onclick event listeners private fun addOnClickListenerOnButton() { - binding.inputDomain.setOnFocusChangeListener { v, hasFocus -> + binding.inputDomain.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { binding.inputDomain.setHint(getString(R.string.placeholder_domain_field)); } else { @@ -45,7 +45,7 @@ class SavePasswordActivity : AppCompatActivity() { } }; - binding.inputUsername.setOnFocusChangeListener { v, hasFocus -> + binding.inputUsername.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { binding.inputUsername.setHint(getString(R.string.placeholder_username_field)); } else { @@ -53,7 +53,7 @@ class SavePasswordActivity : AppCompatActivity() { } }; - binding.inputPassword.setOnFocusChangeListener { v, hasFocus -> + binding.inputPassword.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { binding.inputPassword.setHint(getString(R.string.placeholder_password_field)); } else { @@ -71,28 +71,49 @@ class SavePasswordActivity : AppCompatActivity() { } } - fun performSavePasswordAction(domain: String, username: String, password: String, notes: String) { + fun performSavePasswordAction( + domain: String, + username: String, + password: String, + notes: String + ) { // Launch a coroutine to call the suspend function lifecycleScope.launch { try { val rowId = controller.savePasswordEntity(domain, username, password, notes) // Switch back to Main dispatcher for UI updates withContext(Dispatchers.Main) { - Toast.makeText(this@SavePasswordActivity, "${getString(R.string.success_clause)} $rowId", Toast.LENGTH_SHORT).show() + Toast.makeText( + this@SavePasswordActivity, + "${getString(R.string.success_clause)} $rowId", + Toast.LENGTH_SHORT + ).show() finish() } } catch (e: InvalidInputException) { withContext(Dispatchers.Main) { - Toast.makeText(this@SavePasswordActivity, getString(R.string.warn_fill_form), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@SavePasswordActivity, + getString(R.string.warn_fill_form), + Toast.LENGTH_SHORT + ).show() } } catch (e: DatabaseOperationException) { withContext(Dispatchers.Main) { - Toast.makeText(this@SavePasswordActivity, "${getString(R.string.fail_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@SavePasswordActivity, + "${getString(R.string.fail_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() } } catch (e: Exception) { withContext(Dispatchers.Main) { - Toast.makeText(this@SavePasswordActivity, "${getString(R.string.fail_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@SavePasswordActivity, + "${getString(R.string.fail_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt index 492d7edd..c9f8b356 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt @@ -66,27 +66,38 @@ class SettingsActivity : AppCompatActivity() { } binding.langSwitchDropdown.setSelection(0) - Toast.makeText(this@SettingsActivity, getString(R.string.something_went_wrong_msg), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@SettingsActivity, + getString(R.string.something_went_wrong_msg), + Toast.LENGTH_SHORT + ).show() } // Added all the onclick event listeners private fun addOnClickListenerOnButton() { - binding.langSwitchDropdown.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - val languageTags = resources.getStringArray(R.array.lang_locale_tags) - val localeTag = languageTags[position] - val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(localeTag) - AppCompatDelegate.setApplicationLocales(appLocale) + binding.langSwitchDropdown.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val languageTags = resources.getStringArray(R.array.lang_locale_tags) + val localeTag = languageTags[position] + val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(localeTag) + AppCompatDelegate.setApplicationLocales(appLocale) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Not needed in this case, as we've already set a default + } } - override fun onNothingSelected(parent: AdapterView<*>?) { - // Not needed in this case, as we've already set a default - } - } - binding.toggleThemeBtn.setOnClickListener { val sharedPrefs = getSharedPreferences(Constant.APP_PREFS_NAME, Context.MODE_PRIVATE) - val currentThemeStyle = sharedPrefs.getInt(Constant.THEME_KEY, R.style.PasscodesTheme_Default) + val currentThemeStyle = + sharedPrefs.getInt(Constant.THEME_KEY, R.style.PasscodesTheme_Default) val currentIndex = THEMES.indexOf(currentThemeStyle) val nextIndex = (currentIndex + 1) % THEMES.size @@ -96,20 +107,29 @@ class SettingsActivity : AppCompatActivity() { sharedPrefs.edit { putInt(Constant.THEME_KEY, newThemeStyle) } recreate() - Toast.makeText(this@SettingsActivity, getString(R.string.restart_app_require), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@SettingsActivity, + getString(R.string.restart_app_require), + Toast.LENGTH_SHORT + ).show() } binding.switchLatestFeatures.setOnCheckedChangeListener { _, isChecked -> FeatureFlagManager.get(this).latestFeaturesEnabled = isChecked - Toast.makeText(this@SettingsActivity, getString(R.string.future_feat_clause) + isChecked.toString(), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@SettingsActivity, + getString(R.string.future_feat_clause) + isChecked.toString(), + Toast.LENGTH_SHORT + ).show() } binding.clearAllDataBtn.setOnClickListener { v -> - lifecycleScope.launch { + lifecycleScope.launch { controller.clearAllData() } - Toast.makeText(this@SettingsActivity, "Delete the user data!!", Toast.LENGTH_SHORT).show() + Toast.makeText(this@SettingsActivity, "Delete the user data!!", Toast.LENGTH_SHORT) + .show() } } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index 314bdec1..bf9a2174 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -32,7 +32,8 @@ class UpdatePasswordActivity : AppCompatActivity() { val passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. if (passwordEntityId == -1) { // invalid entity - Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT) + .show() finish() return // Exit onCreate if ID is invalid } @@ -72,11 +73,12 @@ class UpdatePasswordActivity : AppCompatActivity() { val confirmDialog = AlertDialog.Builder(this@UpdatePasswordActivity) .setTitle(R.string.update_password_dialog_title) .setMessage(R.string.irreversible_dialog_desc) - .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> + .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> viewModel.onUpdatePasswordButtonClick() } - .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> - Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT).show(); + .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> + Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) + .show(); } .create() diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt index aece12c7..af394f63 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt @@ -20,13 +20,13 @@ class UpdatePasswordViewModel( private val _usernameState = MutableStateFlow("") val usernameState = _usernameState.asStateFlow() - + private val _passwordState = MutableStateFlow("") val passwordState = _passwordState.asStateFlow() private val _notesState = MutableStateFlow("") val notesState = _notesState.asStateFlow() - + private val _isErrorState = MutableStateFlow(false) val isErrorState = _isErrorState.asStateFlow() @@ -88,6 +88,6 @@ class UpdatePasswordViewModel( true } } - } + } } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt index 536c3471..f4797e67 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt @@ -43,7 +43,8 @@ class ViewPasswordActivity : AppCompatActivity() { passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. if (passwordEntityId == -1) { // invalid entity - Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT) + .show() finish() return // Exit onCreate if ID is invalid } @@ -64,13 +65,21 @@ class ViewPasswordActivity : AppCompatActivity() { try { passwordEntity = controller.getPasswordById(passwordEntityId) withContext(Dispatchers.Main) { - val lastUpdatedAt = DateTimeUtils.getRelativeDays(passwordEntity.updatedAt.orEmpty(), "yyyy-MM-dd HH:mm:ss") - - binding.tvDomain.text = "${getString(R.string.domain_prefix)} ${passwordEntity.domain}" - binding.tvUsername.text = "${getString(R.string.username_prefix)} ${passwordEntity.username}" - binding.tvPassword.text = "${getString(R.string.password_prefix)} ${passwordEntity.password}" - binding.tvNotes.text = "${getString(R.string.notes_prefix)} ${passwordEntity.notes}" - binding.tvUpdatedAt.text = "${getString(R.string.updatedat_prefix)} ${lastUpdatedAt}" + val lastUpdatedAt = DateTimeUtils.getRelativeDays( + passwordEntity.updatedAt.orEmpty(), + "yyyy-MM-dd HH:mm:ss" + ) + + binding.tvDomain.text = + "${getString(R.string.domain_prefix)} ${passwordEntity.domain}" + binding.tvUsername.text = + "${getString(R.string.username_prefix)} ${passwordEntity.username}" + binding.tvPassword.text = + "${getString(R.string.password_prefix)} ${passwordEntity.password}" + binding.tvNotes.text = + "${getString(R.string.notes_prefix)} ${passwordEntity.notes}" + binding.tvUpdatedAt.text = + "${getString(R.string.updatedat_prefix)} ${lastUpdatedAt}" } } catch (e: PasswordNotFoundException) { withContext(Dispatchers.Main) { @@ -80,13 +89,21 @@ class ViewPasswordActivity : AppCompatActivity() { } } catch (e: DatabaseOperationException) { withContext(Dispatchers.Main) { - Toast.makeText(this@ViewPasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@ViewPasswordActivity, + "${getString(R.string.something_went_wrong_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() finish() } } catch (e: Exception) { withContext(Dispatchers.Main) { - Toast.makeText(this@ViewPasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@ViewPasswordActivity, + "${getString(R.string.something_went_wrong_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() finish() } @@ -102,20 +119,24 @@ class ViewPasswordActivity : AppCompatActivity() { val confirmDialog = AlertDialog.Builder(this@ViewPasswordActivity) .setTitle(R.string.copy_password_dialog_title) .setMessage(R.string.danger_copy_to_clipboard_desc) - .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> + .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager - val clip: ClipData = ClipData.newPlainText(passwordEntity.username, passwordEntity.password) + val clip: ClipData = + ClipData.newPlainText(passwordEntity.username, passwordEntity.password) // Set the ClipData to the clipboard if (clipboard != null) { clipboard.setPrimaryClip(clip) - Toast.makeText(this, getString(R.string.copy_success), Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.copy_success), Toast.LENGTH_SHORT) + .show() } else { - Toast.makeText(this, "Clipboard service not available.", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Clipboard service not available.", Toast.LENGTH_SHORT) + .show() } } - .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> - Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT).show(); + .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> + Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) + .show(); } .create(); @@ -132,11 +153,12 @@ class ViewPasswordActivity : AppCompatActivity() { val confirmDialog = AlertDialog.Builder(this@ViewPasswordActivity) .setTitle(R.string.delete_password_dialog_title) .setMessage(R.string.irreversible_dialog_desc) - .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> + .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> performDeletePasswordAction() } - .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> - Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT).show(); + .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> + Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) + .show(); } .create(); @@ -150,21 +172,37 @@ class ViewPasswordActivity : AppCompatActivity() { val rowsDeleted = controller.deletePassword(passwordEntityId) withContext(Dispatchers.Main) { if (rowsDeleted > 0) { - Toast.makeText(this@ViewPasswordActivity, getString(R.string.delete_success_msg), Toast.LENGTH_SHORT).show() + Toast.makeText( + this@ViewPasswordActivity, + getString(R.string.delete_success_msg), + Toast.LENGTH_SHORT + ).show() finish() } else { - Toast.makeText(this@ViewPasswordActivity, getString(R.string.something_went_wrong_msg) + ": Password not found for deletion or no rows affected.", Toast.LENGTH_SHORT).show() + Toast.makeText( + this@ViewPasswordActivity, + getString(R.string.something_went_wrong_msg) + ": Password not found for deletion or no rows affected.", + Toast.LENGTH_SHORT + ).show() finish() } } } catch (e: DatabaseOperationException) { withContext(Dispatchers.Main) { - Toast.makeText(this@ViewPasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@ViewPasswordActivity, + "${getString(R.string.something_went_wrong_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() } } catch (e: Exception) { withContext(Dispatchers.Main) { - Toast.makeText(this@ViewPasswordActivity, "${getString(R.string.something_went_wrong_msg)}: ${e.message}", Toast.LENGTH_LONG).show() + Toast.makeText( + this@ViewPasswordActivity, + "${getString(R.string.something_went_wrong_msg)}: ${e.message}", + Toast.LENGTH_LONG + ).show() e.printStackTrace() } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/CommonUtils.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/CommonUtils.kt index c0644414..e5d7f238 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/CommonUtils.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/CommonUtils.kt @@ -8,7 +8,8 @@ class CommonUtils { companion object { fun getCurrTheme(context: Context): Int { val sharedPrefs = context.getSharedPreferences(Constant.APP_PREFS_NAME, MODE_PRIVATE) - val savedThemeStyle = sharedPrefs.getInt(Constant.THEME_KEY, R.style.PasscodesTheme_Default) + val savedThemeStyle = + sharedPrefs.getInt(Constant.THEME_KEY, R.style.PasscodesTheme_Default) return savedThemeStyle } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt index 84fd0c9b..e773a039 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt @@ -8,10 +8,18 @@ import com.jeeldobariya.passcodes.database.PasswordsDao import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first -class InvalidInputException(message: String = "Input parameters cannot be blank.") : Exception(message) -class DatabaseOperationException(message: String = "A database operation error occurred.", cause: Throwable? = null) : Exception(message, cause) -class PasswordNotFoundException(message: String = "Password with the given ID was not found.") : Exception(message) -class InvalidImportFormat(message: String = "Given Data Is In Invalid Format") : Exception(message) +class InvalidInputException(message: String = "Input parameters cannot be blank.") : + Exception(message) + +class DatabaseOperationException( + message: String = "A database operation error occurred.", + cause: Throwable? = null +) : Exception(message, cause) + +class PasswordNotFoundException(message: String = "Password with the given ID was not found.") : + Exception(message) + +class InvalidImportFormat(message: String = "Given Data Is In Invalid Format") : Exception(message) class Controller(context: Context) { private val passwordsDao: PasswordsDao @@ -32,7 +40,12 @@ class Controller(context: Context) { * @throws InvalidInputException if parameters are blank. * @throws DatabaseOperationException if a database error occurs. */ - suspend fun savePasswordEntity(domain: String, username: String, password: String, notes: String): Long { + suspend fun savePasswordEntity( + domain: String, + username: String, + password: String, + notes: String + ): Long { if (domain.isBlank() || username.isBlank() || password.isBlank()) { throw InvalidInputException() } @@ -96,7 +109,13 @@ class Controller(context: Context) { return passwordsDao.getAllPasswords() } - suspend fun updatePassword(id: Int, domain: String, username: String, password: String, notes: String): Int { + suspend fun updatePassword( + id: Int, + domain: String, + username: String, + password: String, + notes: String + ): Int { if (domain.isBlank() || username.isBlank() || password.isBlank()) { throw InvalidInputException() } @@ -156,7 +175,7 @@ class Controller(context: Context) { if (lines.isEmpty()) { throw InvalidImportFormat("Given data seems to be Empty!!") } - + if (lines[0] != CSV_HEADER) { throw InvalidImportFormat("Given data is not in valid csv format!! correct format:- ${CSV_HEADER}") } @@ -168,12 +187,15 @@ class Controller(context: Context) { val cols = line.split(",") /* NOTE: this need to be done, because our app not allow empty domain. */ - val chosenDomain : String = if (!cols[0].isBlank()) { + val chosenDomain: String = if (!cols[0].isBlank()) { cols[0].trim() // used: name } else cols[1].trim() // used: url try { - val password: Password? = passwordsDao.getPasswordByUsernameAndDomain(username = cols[2].trim(), domain = chosenDomain) + val password: Password? = passwordsDao.getPasswordByUsernameAndDomain( + username = cols[2].trim(), + domain = chosenDomain + ) if (password != null) { updatePassword( diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Permissions.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Permissions.kt index 64bddbd2..db1e16cc 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Permissions.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Permissions.kt @@ -17,8 +17,10 @@ class Permissions(private val activity: Activity) { * @return True if permissions are granted, false otherwise. */ fun checkPermission(): Boolean { - val resultWrite = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) - val resultRead = ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) + val resultWrite = + ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) + val resultRead = + ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) return resultWrite == PackageManager.PERMISSION_GRANTED && resultRead == PackageManager.PERMISSION_GRANTED } @@ -28,7 +30,10 @@ class Permissions(private val activity: Activity) { fun requestPermission() { ActivityCompat.requestPermissions( activity, - arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE), + arrayOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + ), PERMISSION_REQUEST_CODE ) } @@ -43,8 +48,8 @@ class Permissions(private val activity: Activity) { // We expect two permissions (WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE) // and check if both were granted. return grantResults.isNotEmpty() && - grantResults.size >= 2 && // Ensure we got results for at least two permissions - grantResults[0] == PackageManager.PERMISSION_GRANTED && - grantResults[1] == PackageManager.PERMISSION_GRANTED + grantResults.size >= 2 && // Ensure we got results for at least two permissions + grantResults[0] == PackageManager.PERMISSION_GRANTED && + grantResults[1] == PackageManager.PERMISSION_GRANTED } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt index 37090715..b08a5ea0 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -57,7 +57,10 @@ object UpdateChecker { } if (!userReleaseFound) { - showToast(appcontext, "⚠️ Version ($currentNormalizeVersion) not found on GitHub releases... Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})") + showToast( + appcontext, + "⚠️ Version ($currentNormalizeVersion) not found on GitHub releases... Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})" + ) } } }) From d99bac3c6b1c2e9c84409b63d76b8470ed545eff Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Wed, 8 Oct 2025 18:43:00 +0530 Subject: [PATCH 10/15] feat: create a viewmodel for savePasswordActivity --- .../jeeldobariya/passcodes/di/appModule.kt | 5 ++ .../passcodes/ui/SavePasswordActivity.kt | 90 ++++--------------- .../passcodes/ui/SavePasswordViewModel.kt | 69 ++++++++++++++ 3 files changed, 91 insertions(+), 73 deletions(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt index b93d0a36..257b9420 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt @@ -1,5 +1,6 @@ package com.jeeldobariya.passcodes.di +import com.jeeldobariya.passcodes.ui.SavePasswordViewModel import com.jeeldobariya.passcodes.ui.UpdatePasswordViewModel import com.jeeldobariya.passcodes.utils.Controller import org.koin.android.ext.koin.androidContext @@ -16,4 +17,8 @@ val appModule = module { UpdatePasswordViewModel(get()) } + viewModel { + SavePasswordViewModel(get()) + } + } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt index 37301ee8..2a767bd6 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt @@ -1,23 +1,18 @@ package com.jeeldobariya.passcodes.ui import android.os.Bundle -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import androidx.lifecycle.lifecycleScope import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.databinding.ActivitySavePasswordBinding import com.jeeldobariya.passcodes.utils.CommonUtils -import com.jeeldobariya.passcodes.utils.Controller -import com.jeeldobariya.passcodes.utils.DatabaseOperationException -import com.jeeldobariya.passcodes.utils.InvalidInputException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import org.koin.androidx.viewmodel.ext.android.viewModel +import kotlin.getValue class SavePasswordActivity : AppCompatActivity() { - private lateinit var controller: Controller + private val viewModel: SavePasswordViewModel by viewModel() + private lateinit var binding: ActivitySavePasswordBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -26,17 +21,6 @@ class SavePasswordActivity : AppCompatActivity() { binding = ActivitySavePasswordBinding.inflate(layoutInflater) setContentView(binding.root) - controller = Controller(this) // Initialize controller - - // Add event onclick listener - addOnClickListenerOnButton() - - // Make window fullscreen - WindowCompat.setDecorFitsSystemWindows(window, false) - } - - // Added all the onclick event listeners - private fun addOnClickListenerOnButton() { binding.inputDomain.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { binding.inputDomain.setHint(getString(R.string.placeholder_domain_field)); @@ -61,62 +45,22 @@ class SavePasswordActivity : AppCompatActivity() { } }; - binding.savePasswordBtn.setOnClickListener { - val domain = binding.inputDomain.text.toString() - val username = binding.inputUsername.text.toString() - val password = binding.inputPassword.text.toString() - val notes = binding.inputNotes.text.toString() + // Add event onclick listener + addOnClickListenerOnButton() - performSavePasswordAction(domain, username, password, notes) - } + // Make window fullscreen + WindowCompat.setDecorFitsSystemWindows(window, false) } - fun performSavePasswordAction( - domain: String, - username: String, - password: String, - notes: String - ) { - // Launch a coroutine to call the suspend function - lifecycleScope.launch { - try { - val rowId = controller.savePasswordEntity(domain, username, password, notes) - // Switch back to Main dispatcher for UI updates - withContext(Dispatchers.Main) { - Toast.makeText( - this@SavePasswordActivity, - "${getString(R.string.success_clause)} $rowId", - Toast.LENGTH_SHORT - ).show() - finish() - } - } catch (e: InvalidInputException) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@SavePasswordActivity, - getString(R.string.warn_fill_form), - Toast.LENGTH_SHORT - ).show() - } - } catch (e: DatabaseOperationException) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@SavePasswordActivity, - "${getString(R.string.fail_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@SavePasswordActivity, - "${getString(R.string.fail_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - } - } + // Added all the onclick event listeners + private fun addOnClickListenerOnButton() { + binding.savePasswordBtn.setOnClickListener { + viewModel.onChangeDomainText(binding.inputDomain.text.toString()) + viewModel.onChangeUsernameText(binding.inputUsername.text.toString()) + viewModel.onChangePasswordText(binding.inputPassword.text.toString()) + viewModel.onChangeNotesText(binding.inputNotes.text.toString()) + + viewModel.onSavePasswordButtonClick() } } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt new file mode 100644 index 00000000..ee1de373 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt @@ -0,0 +1,69 @@ +package com.jeeldobariya.passcodes.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jeeldobariya.passcodes.utils.Controller +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class SavePasswordViewModel ( + val controller: Controller +) : ViewModel() { + private val _domainState = MutableStateFlow("") + val domainState = _domainState.asStateFlow() + + private val _usernameState = MutableStateFlow("") + val usernameState = _usernameState.asStateFlow() + + private val _passwordState = MutableStateFlow("") + val passwordState = _passwordState.asStateFlow() + + private val _notesState = MutableStateFlow("") + val notesState = _notesState.asStateFlow() + + private val _isErrorState = MutableStateFlow(false) + val isErrorState = _isErrorState.asStateFlow() + + fun onChangeDomainText(text: String) { + _domainState.update { + text + } + } + + fun onChangeUsernameText(text: String) { + _usernameState.update { + text + } + } + + fun onChangePasswordText(text: String) { + _passwordState.update { + text + } + } + + fun onChangeNotesText(text: String) { + _notesState.update { + text + } + } + + fun onSavePasswordButtonClick() { + viewModelScope.launch { + try { + val rowsAffected = controller.savePasswordEntity( + _domainState.value, + _usernameState.value, + _passwordState.value, + _notesState.value + ) + } catch (e: Exception) { + _isErrorState.update { + true + } + } + } + } +} From 73bb82aa6f41e338e9c2342919b60c90102be4d9 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Wed, 8 Oct 2025 18:54:21 +0530 Subject: [PATCH 11/15] refactor: improve viewmodel of save passwords --- .../jeeldobariya/passcodes/ui/SavePasswordViewModel.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt index ee1de373..2d1cb3e7 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt @@ -12,19 +12,19 @@ class SavePasswordViewModel ( val controller: Controller ) : ViewModel() { private val _domainState = MutableStateFlow("") - val domainState = _domainState.asStateFlow() + // val domainState = _domainState.asStateFlow() private val _usernameState = MutableStateFlow("") - val usernameState = _usernameState.asStateFlow() + // val usernameState = _usernameState.asStateFlow() private val _passwordState = MutableStateFlow("") - val passwordState = _passwordState.asStateFlow() + // val passwordState = _passwordState.asStateFlow() private val _notesState = MutableStateFlow("") - val notesState = _notesState.asStateFlow() + // val notesState = _notesState.asStateFlow() private val _isErrorState = MutableStateFlow(false) - val isErrorState = _isErrorState.asStateFlow() + // val isErrorState = _isErrorState.asStateFlow() fun onChangeDomainText(text: String) { _domainState.update { From e98907cb41e1a792e028c6738f637e66e3ca0d9b Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Wed, 8 Oct 2025 19:16:06 +0530 Subject: [PATCH 12/15] feat: create a load password viewmodel --- .../jeeldobariya/passcodes/di/appModule.kt | 5 ++ .../passcodes/ui/LoadPasswordActivity.kt | 78 +++++++------------ .../passcodes/ui/LoadPasswordViewModel.kt | 35 +++++++++ 3 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt index 257b9420..3d74c5f8 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt @@ -1,5 +1,6 @@ package com.jeeldobariya.passcodes.di +import com.jeeldobariya.passcodes.ui.LoadPasswordViewModel import com.jeeldobariya.passcodes.ui.SavePasswordViewModel import com.jeeldobariya.passcodes.ui.UpdatePasswordViewModel import com.jeeldobariya.passcodes.utils.Controller @@ -21,4 +22,8 @@ val appModule = module { SavePasswordViewModel(get()) } + viewModel { + LoadPasswordViewModel(get()) + } + } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt index 917017fb..068896ec 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt @@ -14,16 +14,19 @@ import com.jeeldobariya.passcodes.ui.adapter.PasswordAdapter import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.Controller import com.jeeldobariya.passcodes.utils.DatabaseOperationException +import com.jeeldobariya.passcodes.utils.collectLatestLifecycleFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.flow.catch +import org.koin.androidx.viewmodel.ext.android.viewModel class LoadPasswordActivity : AppCompatActivity() { + private val viewModel: LoadPasswordViewModel by viewModel() + private lateinit var binding: ActivityLoadPasswordBinding private lateinit var passwordAdapter: PasswordAdapter - private lateinit var controller: Controller override fun onCreate(savedInstanceState: Bundle?) { CommonUtils.updateCurrTheme(this) @@ -31,52 +34,37 @@ class LoadPasswordActivity : AppCompatActivity() { binding = ActivityLoadPasswordBinding.inflate(layoutInflater) setContentView(binding.root) - controller = Controller(this) // Initialize the controller here + collectLatestLifecycleFlow(viewModel.passwordsListState) { passwordList -> + if (!this@LoadPasswordActivity::passwordAdapter.isInitialized) { + passwordAdapter = + PasswordAdapter(this@LoadPasswordActivity, passwordList) + binding.passwordList.adapter = passwordAdapter + } else { + passwordAdapter.updateData(passwordList) + } + } + + collectLatestLifecycleFlow(viewModel.isErrorState) { error -> + if (error) { + Toast.makeText( + this@LoadPasswordActivity, + "${getString(R.string.something_went_wrong_msg)}", + Toast.LENGTH_LONG + ).show() + } + } // Add event onclick listener addOnClickListenerOnButton() // Make window fullscreen WindowCompat.setDecorFitsSystemWindows(window, false) - - // Start collecting the password list Flow when the activity is created - // This collection will automatically update the UI when database changes occur. - collectPasswordList() } - private fun collectPasswordList() { - lifecycleScope.launch { - controller.getAllPasswords() - .catch { e -> - withContext(Dispatchers.Main) { - Toast.makeText( - this@LoadPasswordActivity, - "${getString(R.string.something_went_wrong_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - // Ensure adapter is initialized with empty list on error - if (!this@LoadPasswordActivity::passwordAdapter.isInitialized) { - passwordAdapter = - PasswordAdapter(this@LoadPasswordActivity, emptyList()) - binding.passwordList.adapter = passwordAdapter - } - } - } - .collect { passwordList -> - // This block will be executed every time the list of passwords changes in the database - // and emitted by the Flow. - withContext(Dispatchers.Main) { // Ensure UI updates are on the main thread - if (!this@LoadPasswordActivity::passwordAdapter.isInitialized) { - passwordAdapter = - PasswordAdapter(this@LoadPasswordActivity, passwordList) - binding.passwordList.adapter = passwordAdapter - } else { - passwordAdapter.updateData(passwordList) - } - } - } - } + override fun onResume() { + super.onResume() + + viewModel.loadInitialData() } // Added all the onclick event listeners @@ -91,16 +79,4 @@ class LoadPasswordActivity : AppCompatActivity() { startActivity(intent) } } - - // onResume is no longer needed to explicitly call fillPasswordList() - // because Flow collection started in onCreate will handle updates. - // However, if your activity might be killed and recreated, the onCreate will - // re-initiate the collection. For simple cases, this is fine. - // If you need to stop collection when the activity goes to background, - // and restart on foreground, you might manage the coroutine job more explicitly. - // But lifecycleScope handles stopping on destroy for you. - // In this specific setup, `onResume`'s `super.onResume()` is enough. - // No need for a custom `onResume` override anymore if it only contained `fillPasswordList()` - // and `collectPasswordList()` is in `onCreate`. - // Removed the `onResume` override as it's now redundant with Flow collection in onCreate. } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt new file mode 100644 index 00000000..53cc88d0 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt @@ -0,0 +1,35 @@ +package com.jeeldobariya.passcodes.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jeeldobariya.passcodes.database.Password +import com.jeeldobariya.passcodes.utils.Controller +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class LoadPasswordViewModel( + var controller: Controller +): ViewModel() { + + private val _passwordsListState = MutableStateFlow(emptyList()) + val passwordsListState = _passwordsListState.asStateFlow() + + private val _isErrorState = MutableStateFlow(false) + val isErrorState = _isErrorState.asStateFlow() + + fun loadInitialData() { + viewModelScope.launch { + _passwordsListState.update { + controller.getAllPasswords().catch { + _isErrorState.update { + true + } + }.first() + } + } + } +} From 6b73097d34ead23df26a6b3610d07ad239eac9c2 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Wed, 8 Oct 2025 19:28:06 +0530 Subject: [PATCH 13/15] fix: screen not direct back after it job is done --- .../com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt | 3 +++ .../com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt | 2 +- .../com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt index 2a767bd6..8989649f 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt @@ -61,6 +61,9 @@ class SavePasswordActivity : AppCompatActivity() { viewModel.onChangeNotesText(binding.inputNotes.text.toString()) viewModel.onSavePasswordButtonClick() + if (!viewModel.isErrorState.value) { + finish() + } } } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt index 2d1cb3e7..39c15ae7 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt @@ -24,7 +24,7 @@ class SavePasswordViewModel ( // val notesState = _notesState.asStateFlow() private val _isErrorState = MutableStateFlow(false) - // val isErrorState = _isErrorState.asStateFlow() + val isErrorState = _isErrorState.asStateFlow() fun onChangeDomainText(text: String) { _domainState.update { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index bf9a2174..f8ac4895 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -75,6 +75,9 @@ class UpdatePasswordActivity : AppCompatActivity() { .setMessage(R.string.irreversible_dialog_desc) .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> viewModel.onUpdatePasswordButtonClick() + if (!viewModel.isErrorState.value) { + finish() + } } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) From eef7d5b82a8389f3c80dbf838c0a90bc3a683a71 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 9 Oct 2025 21:11:19 +0530 Subject: [PATCH 14/15] feat: create a view password viewmodel --- .../jeeldobariya/passcodes/di/appModule.kt | 5 + .../passcodes/ui/ViewPasswordActivity.kt | 178 ++++++------------ .../passcodes/ui/ViewPasswordViewModel.kt | 69 +++++++ 3 files changed, 127 insertions(+), 125 deletions(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt index 3d74c5f8..04dc71b1 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/di/appModule.kt @@ -3,6 +3,7 @@ package com.jeeldobariya.passcodes.di import com.jeeldobariya.passcodes.ui.LoadPasswordViewModel import com.jeeldobariya.passcodes.ui.SavePasswordViewModel import com.jeeldobariya.passcodes.ui.UpdatePasswordViewModel +import com.jeeldobariya.passcodes.ui.ViewPasswordViewModel import com.jeeldobariya.passcodes.utils.Controller import org.koin.android.ext.koin.androidContext import org.koin.core.module.dsl.viewModel @@ -26,4 +27,8 @@ val appModule = module { LoadPasswordViewModel(get()) } + viewModel { + ViewPasswordViewModel(get()) + } + } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt index f4797e67..ac91e0bd 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt @@ -1,46 +1,41 @@ package com.jeeldobariya.passcodes.ui -import android.content.ClipData; +import android.content.ClipData import android.content.ClipboardManager -import android.content.Context import android.content.Intent import android.os.Bundle import android.widget.Toast import android.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import androidx.lifecycle.lifecycleScope import com.jeeldobariya.passcodes.R -import com.jeeldobariya.passcodes.database.Password import com.jeeldobariya.passcodes.databinding.ActivityViewPasswordBinding import com.jeeldobariya.passcodes.flags.FeatureFlagManager import com.jeeldobariya.passcodes.utils.CommonUtils -import com.jeeldobariya.passcodes.utils.Controller -import com.jeeldobariya.passcodes.utils.DatabaseOperationException -import com.jeeldobariya.passcodes.utils.DateTimeUtils -import com.jeeldobariya.passcodes.utils.PasswordNotFoundException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import com.jeeldobariya.passcodes.utils.collectLatestLifecycleFlow +import kotlinx.coroutines.runBlocking +import org.koin.androidx.viewmodel.ext.android.viewModel +import kotlin.getValue /* * Activity expects id as intent parameters. */ class ViewPasswordActivity : AppCompatActivity() { - private var passwordEntityId: Int = 0 + private val viewModel: ViewPasswordViewModel by viewModel() + private lateinit var binding: ActivityViewPasswordBinding - private lateinit var controller: Controller - private lateinit var passwordEntity: Password + override fun onCreate(savedInstanceState: Bundle?) { CommonUtils.updateCurrTheme(this) + super.onCreate(savedInstanceState) binding = ActivityViewPasswordBinding.inflate(layoutInflater) setContentView(binding.root) val intent = intent - passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. + val passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. if (passwordEntityId == -1) { // invalid entity Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT) @@ -49,7 +44,38 @@ class ViewPasswordActivity : AppCompatActivity() { return // Exit onCreate if ID is invalid } - controller = Controller(this) + viewModel.loadInitialData(passwordEntityId) + + collectLatestLifecycleFlow(viewModel.domainState) { domain -> + binding.tvDomain.text = + "${getString(R.string.domain_prefix)} $domain" + } + collectLatestLifecycleFlow(viewModel.usernameState) { username -> + binding.tvUsername.text = + "${getString(R.string.username_prefix)} $username" + } + collectLatestLifecycleFlow(viewModel.passwordState) { password -> + binding.tvPassword.text = + "${getString(R.string.password_prefix)} $password" + } + collectLatestLifecycleFlow(viewModel.notesState) { notes -> + binding.tvNotes.text = + "${getString(R.string.notes_prefix)} $notes" + } + collectLatestLifecycleFlow(viewModel.lastUpdatedAtState) { lastUpdatedAt -> + binding.tvUpdatedAt.text = + "${getString(R.string.updatedat_prefix)} $lastUpdatedAt" + } + collectLatestLifecycleFlow(viewModel.isErrorState) { error -> + if (error) { + Toast.makeText( + this@ViewPasswordActivity, + getString(R.string.something_went_wrong_msg), + Toast.LENGTH_LONG + ).show() + finish() + } + } binding.copyPasswordBtn.isEnabled = FeatureFlagManager.get(this).latestFeaturesEnabled @@ -60,57 +86,6 @@ class ViewPasswordActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) } - private fun fillDataInTextview() { - lifecycleScope.launch { - try { - passwordEntity = controller.getPasswordById(passwordEntityId) - withContext(Dispatchers.Main) { - val lastUpdatedAt = DateTimeUtils.getRelativeDays( - passwordEntity.updatedAt.orEmpty(), - "yyyy-MM-dd HH:mm:ss" - ) - - binding.tvDomain.text = - "${getString(R.string.domain_prefix)} ${passwordEntity.domain}" - binding.tvUsername.text = - "${getString(R.string.username_prefix)} ${passwordEntity.username}" - binding.tvPassword.text = - "${getString(R.string.password_prefix)} ${passwordEntity.password}" - binding.tvNotes.text = - "${getString(R.string.notes_prefix)} ${passwordEntity.notes}" - binding.tvUpdatedAt.text = - "${getString(R.string.updatedat_prefix)} ${lastUpdatedAt}" - } - } catch (e: PasswordNotFoundException) { - withContext(Dispatchers.Main) { - Toast.makeText(this@ViewPasswordActivity, e.message, Toast.LENGTH_LONG).show() - e.printStackTrace() - finish() - } - } catch (e: DatabaseOperationException) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@ViewPasswordActivity, - "${getString(R.string.something_went_wrong_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - finish() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@ViewPasswordActivity, - "${getString(R.string.something_went_wrong_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - finish() - } - } - } - } - // Added all the onclick event listeners private fun addOnClickListenerOnButton() { binding.copyPasswordBtn.setOnClickListener { @@ -120,9 +95,9 @@ class ViewPasswordActivity : AppCompatActivity() { .setTitle(R.string.copy_password_dialog_title) .setMessage(R.string.danger_copy_to_clipboard_desc) .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val clipboard = getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager val clip: ClipData = - ClipData.newPlainText(passwordEntity.username, passwordEntity.password) + ClipData.newPlainText(viewModel.usernameState.value, viewModel.passwordState.value) // Set the ClipData to the clipboard if (clipboard != null) { @@ -136,16 +111,16 @@ class ViewPasswordActivity : AppCompatActivity() { } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) - .show(); + .show() } - .create(); + .create() - confirmDialog.show(); + confirmDialog.show() } binding.updatePasswordBtn.setOnClickListener { val viewPasswordIntent = Intent(this, UpdatePasswordActivity::class.java) - viewPasswordIntent.putExtra("id", passwordEntityId) + viewPasswordIntent.putExtra("id", viewModel.passwordEntityId) startActivity(viewPasswordIntent) } @@ -154,63 +129,16 @@ class ViewPasswordActivity : AppCompatActivity() { .setTitle(R.string.delete_password_dialog_title) .setMessage(R.string.irreversible_dialog_desc) .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> - performDeletePasswordAction() + runBlocking { viewModel.onDeletePasswordButtonClick() } + finish() } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) - .show(); + .show() } - .create(); + .create() - confirmDialog.show(); + confirmDialog.show() } } - - private fun performDeletePasswordAction() { - lifecycleScope.launch { - try { - val rowsDeleted = controller.deletePassword(passwordEntityId) - withContext(Dispatchers.Main) { - if (rowsDeleted > 0) { - Toast.makeText( - this@ViewPasswordActivity, - getString(R.string.delete_success_msg), - Toast.LENGTH_SHORT - ).show() - finish() - } else { - Toast.makeText( - this@ViewPasswordActivity, - getString(R.string.something_went_wrong_msg) + ": Password not found for deletion or no rows affected.", - Toast.LENGTH_SHORT - ).show() - finish() - } - } - } catch (e: DatabaseOperationException) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@ViewPasswordActivity, - "${getString(R.string.something_went_wrong_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - Toast.makeText( - this@ViewPasswordActivity, - "${getString(R.string.something_went_wrong_msg)}: ${e.message}", - Toast.LENGTH_LONG - ).show() - e.printStackTrace() - } - } - } - } - - override fun onResume() { - super.onResume() - fillDataInTextview() - } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt new file mode 100644 index 00000000..b32b3e33 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt @@ -0,0 +1,69 @@ +package com.jeeldobariya.passcodes.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jeeldobariya.passcodes.database.Password +import com.jeeldobariya.passcodes.utils.Controller +import com.jeeldobariya.passcodes.utils.DateTimeUtils +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class ViewPasswordViewModel( + val controller: Controller +): ViewModel() { + var passwordEntityId: Int = -1 + + private val _domainState = MutableStateFlow("") + val domainState = _domainState.asStateFlow() + + private val _usernameState = MutableStateFlow("") + val usernameState = _usernameState.asStateFlow() + + private val _passwordState = MutableStateFlow("") + val passwordState = _passwordState.asStateFlow() + + private val _notesState = MutableStateFlow("") + val notesState = _notesState.asStateFlow() + + private val _lastUpdatedAtState = MutableStateFlow("") + val lastUpdatedAtState = _lastUpdatedAtState.asStateFlow() + + private val _isErrorState = MutableStateFlow(false) + val isErrorState = _isErrorState.asStateFlow() + + fun loadInitialData(passwordId: Int) { + passwordEntityId = passwordId + + viewModelScope.launch { + try { + val password: Password = controller.getPasswordById(passwordId) + + _domainState.update { password.domain } + _usernameState.update { password.username } + _passwordState.update { password.password } + _notesState.update { password.notes } + _lastUpdatedAtState.update { + DateTimeUtils.getRelativeDays(password.updatedAt.orEmpty()) + } + } catch (_: Exception) { + _isErrorState.update { + true + } + } + } + } + + fun onDeletePasswordButtonClick() { + viewModelScope.launch { + try { + val rowsDeleted = controller.deletePassword(passwordEntityId) + } catch (_: Exception) { + _isErrorState.update { + true + } + } + } + } +} From f6fcf700c58ab08cb332b1f6cad9044930c458ed Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 9 Oct 2025 21:16:32 +0530 Subject: [PATCH 15/15] lint: run the android studio reformat feature --- .../autofill/AutofillSettingsActivity.kt | 2 +- .../autofill/PasswordAutofillService.kt | 2 -- .../passcodes/database/MasterDatabase.kt | 10 +++++----- .../passcodes/database/PasswordsDao.kt | 2 +- .../passcodes/ui/AboutUsActivity.kt | 4 ++-- .../passcodes/ui/LoadPasswordActivity.kt | 8 -------- .../passcodes/ui/LoadPasswordViewModel.kt | 2 +- .../jeeldobariya/passcodes/ui/MainActivity.kt | 10 +++------- .../passcodes/ui/PasswordManagerActivity.kt | 3 +-- .../passcodes/ui/SavePasswordActivity.kt | 19 +++++++++---------- .../passcodes/ui/SavePasswordViewModel.kt | 4 ++-- .../passcodes/ui/SettingsActivity.kt | 12 +++++------- .../passcodes/ui/UpdatePasswordActivity.kt | 8 ++++---- .../passcodes/ui/UpdatePasswordViewModel.kt | 2 +- .../passcodes/ui/ViewPasswordActivity.kt | 12 +++++++----- .../passcodes/ui/ViewPasswordViewModel.kt | 4 ++-- .../passcodes/utils/Controller.kt | 1 - .../passcodes/utils/UpdateChecker.kt | 6 +++++- 18 files changed, 49 insertions(+), 62 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt index 13472f86..3319e917 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/AutofillSettingsActivity.kt @@ -4,8 +4,8 @@ import android.content.Intent import android.os.Bundle import android.provider.Settings import androidx.appcompat.app.AppCompatActivity -import com.jeeldobariya.passcodes.R import com.google.android.material.button.MaterialButton +import com.jeeldobariya.passcodes.R class AutofillSettingsActivity : AppCompatActivity() { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt index 2a28456e..85af800b 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/autofill/PasswordAutofillService.kt @@ -3,9 +3,7 @@ package com.jeeldobariya.passcodes.autofill import android.app.assist.AssistStructure import android.os.CancellationSignal import android.service.autofill.AutofillService -import android.service.autofill.Dataset import android.service.autofill.FillCallback -import android.service.autofill.FillContext import android.service.autofill.FillRequest import android.service.autofill.FillResponse import android.service.autofill.SaveCallback diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt index b193ddd9..32515b70 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/MasterDatabase.kt @@ -1,9 +1,9 @@ -package com.jeeldobariya.passcodes.database; +package com.jeeldobariya.passcodes.database -import android.content.Context; -import androidx.room.Database; -import androidx.room.Room; -import androidx.room.RoomDatabase; +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase @Database( entities = [Password::class], diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/PasswordsDao.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/PasswordsDao.kt index 1c7ba6c9..55b246c7 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/database/PasswordsDao.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/database/PasswordsDao.kt @@ -1,10 +1,10 @@ package com.jeeldobariya.passcodes.database import androidx.room.Dao +import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Update -import androidx.room.Delete import kotlinx.coroutines.flow.Flow @Dao diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt index cea7a3ef..8fb28137 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/AboutUsActivity.kt @@ -1,12 +1,12 @@ package com.jeeldobariya.passcodes.ui -import com.jeeldobariya.passcodes.databinding.ActivityAboutUsBinding -import com.jeeldobariya.passcodes.utils.Constant import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri +import com.jeeldobariya.passcodes.databinding.ActivityAboutUsBinding import com.jeeldobariya.passcodes.utils.CommonUtils +import com.jeeldobariya.passcodes.utils.Constant class AboutUsActivity : AppCompatActivity() { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt index 068896ec..39491487 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordActivity.kt @@ -1,24 +1,16 @@ package com.jeeldobariya.passcodes.ui -import android.content.Context import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import androidx.lifecycle.lifecycleScope import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.database.Password import com.jeeldobariya.passcodes.databinding.ActivityLoadPasswordBinding import com.jeeldobariya.passcodes.ui.adapter.PasswordAdapter import com.jeeldobariya.passcodes.utils.CommonUtils -import com.jeeldobariya.passcodes.utils.Controller -import com.jeeldobariya.passcodes.utils.DatabaseOperationException import com.jeeldobariya.passcodes.utils.collectLatestLifecycleFlow -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.coroutines.flow.catch import org.koin.androidx.viewmodel.ext.android.viewModel class LoadPasswordActivity : AppCompatActivity() { diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt index 53cc88d0..e682e89b 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/LoadPasswordViewModel.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch class LoadPasswordViewModel( var controller: Controller -): ViewModel() { +) : ViewModel() { private val _passwordsListState = MutableStateFlow(emptyList()) val passwordsListState = _passwordsListState.asStateFlow() diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt index 8d97690a..1789a3f8 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt @@ -1,20 +1,16 @@ package com.jeeldobariya.passcodes.ui -import android.content.Context -import android.os.Bundle import android.content.Intent +import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import android.view.LayoutInflater import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.BuildConfig import com.jeeldobariya.passcodes.databinding.ActivityMainBinding import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.UpdateChecker +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch // import com.jeeldobariya.passcodes.utils.Permissions diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt index e31fbf77..e28777d2 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt @@ -1,9 +1,8 @@ package com.jeeldobariya.passcodes.ui import android.content.Intent -import android.view.View.GONE import android.os.Bundle -import android.util.Log +import android.view.View.GONE import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt index 8989649f..7b9c85fb 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordActivity.kt @@ -7,7 +7,6 @@ import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.databinding.ActivitySavePasswordBinding import com.jeeldobariya.passcodes.utils.CommonUtils import org.koin.androidx.viewmodel.ext.android.viewModel -import kotlin.getValue class SavePasswordActivity : AppCompatActivity() { @@ -23,27 +22,27 @@ class SavePasswordActivity : AppCompatActivity() { binding.inputDomain.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { - binding.inputDomain.setHint(getString(R.string.placeholder_domain_field)); + binding.inputDomain.setHint(getString(R.string.placeholder_domain_field)) } else { - binding.inputDomain.setHint(""); + binding.inputDomain.setHint("") } - }; + } binding.inputUsername.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { - binding.inputUsername.setHint(getString(R.string.placeholder_username_field)); + binding.inputUsername.setHint(getString(R.string.placeholder_username_field)) } else { - binding.inputUsername.setHint(""); + binding.inputUsername.setHint("") } - }; + } binding.inputPassword.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { - binding.inputPassword.setHint(getString(R.string.placeholder_password_field)); + binding.inputPassword.setHint(getString(R.string.placeholder_password_field)) } else { - binding.inputPassword.setHint(""); + binding.inputPassword.setHint("") } - }; + } // Add event onclick listener addOnClickListenerOnButton() diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt index 39c15ae7..793d648a 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SavePasswordViewModel.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class SavePasswordViewModel ( +class SavePasswordViewModel( val controller: Controller ) : ViewModel() { private val _domainState = MutableStateFlow("") @@ -53,7 +53,7 @@ class SavePasswordViewModel ( fun onSavePasswordButtonClick() { viewModelScope.launch { try { - val rowsAffected = controller.savePasswordEntity( + controller.savePasswordEntity( _domainState.value, _usernameState.value, _passwordState.value, diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt index c9f8b356..ff164a16 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt @@ -2,20 +2,18 @@ package com.jeeldobariya.passcodes.ui import android.content.Context import android.os.Bundle -import android.widget.Toast +import android.view.View import android.widget.AdapterView +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.edit import androidx.core.os.LocaleListCompat -import android.view.View import androidx.core.view.WindowCompat -import android.view.LayoutInflater - +import androidx.lifecycle.lifecycleScope import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.databinding.ActivitySettingsBinding import com.jeeldobariya.passcodes.flags.FeatureFlagManager -import androidx.core.content.edit -import androidx.lifecycle.lifecycleScope import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.Constant import com.jeeldobariya.passcodes.utils.Controller @@ -95,7 +93,7 @@ class SettingsActivity : AppCompatActivity() { } binding.toggleThemeBtn.setOnClickListener { - val sharedPrefs = getSharedPreferences(Constant.APP_PREFS_NAME, Context.MODE_PRIVATE) + val sharedPrefs = getSharedPreferences(Constant.APP_PREFS_NAME, MODE_PRIVATE) val currentThemeStyle = sharedPrefs.getInt(Constant.THEME_KEY, R.style.PasscodesTheme_Default) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt index f8ac4895..287139ee 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordActivity.kt @@ -1,8 +1,8 @@ package com.jeeldobariya.passcodes.ui +import android.app.AlertDialog import android.os.Bundle import android.widget.Toast -import android.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import com.jeeldobariya.passcodes.R @@ -40,7 +40,7 @@ class UpdatePasswordActivity : AppCompatActivity() { viewModel.loadInitialData(passwordEntityId) - binding.tvId.setText("${getString(R.string.id_prefix)} ${viewModel.passwordEntityId}") + binding.tvId.text = "${getString(R.string.id_prefix)} ${viewModel.passwordEntityId}" collectLatestLifecycleFlow(viewModel.domainState) { domain -> binding.inputDomain.setText(domain) @@ -81,11 +81,11 @@ class UpdatePasswordActivity : AppCompatActivity() { } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> Toast.makeText(this, getString(R.string.action_discard), Toast.LENGTH_SHORT) - .show(); + .show() } .create() - confirmDialog.show(); + confirmDialog.show() } } } diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt index af394f63..94800511 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/UpdatePasswordViewModel.kt @@ -76,7 +76,7 @@ class UpdatePasswordViewModel( fun onUpdatePasswordButtonClick() { viewModelScope.launch { try { - val rowsAffected = controller.updatePassword( + controller.updatePassword( passwordEntityId, _domainState.value, _usernameState.value, diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt index ac91e0bd..54138ccd 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt @@ -1,11 +1,11 @@ package com.jeeldobariya.passcodes.ui +import android.app.AlertDialog import android.content.ClipData import android.content.ClipboardManager import android.content.Intent import android.os.Bundle import android.widget.Toast -import android.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import com.jeeldobariya.passcodes.R @@ -15,7 +15,6 @@ import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.collectLatestLifecycleFlow import kotlinx.coroutines.runBlocking import org.koin.androidx.viewmodel.ext.android.viewModel -import kotlin.getValue /* * Activity expects id as intent parameters. @@ -35,7 +34,7 @@ class ViewPasswordActivity : AppCompatActivity() { setContentView(binding.root) val intent = intent - val passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. + val passwordEntityId = intent.getIntExtra("id", -1) // -1 is an invalid id. if (passwordEntityId == -1) { // invalid entity Toast.makeText(this, getString(R.string.error_invalid_password_id), Toast.LENGTH_SHORT) @@ -97,7 +96,10 @@ class ViewPasswordActivity : AppCompatActivity() { .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> val clipboard = getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager val clip: ClipData = - ClipData.newPlainText(viewModel.usernameState.value, viewModel.passwordState.value) + ClipData.newPlainText( + viewModel.usernameState.value, + viewModel.passwordState.value + ) // Set the ClipData to the clipboard if (clipboard != null) { @@ -129,7 +131,7 @@ class ViewPasswordActivity : AppCompatActivity() { .setTitle(R.string.delete_password_dialog_title) .setMessage(R.string.irreversible_dialog_desc) .setPositiveButton(R.string.confirm_dialog_button_text) { dialog, which -> - runBlocking { viewModel.onDeletePasswordButtonClick() } + runBlocking { viewModel.onDeletePasswordButtonClick() } finish() } .setNegativeButton(R.string.discard_dialog_button_text) { dialog, which -> diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt index b32b3e33..425c82e0 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordViewModel.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.launch class ViewPasswordViewModel( val controller: Controller -): ViewModel() { +) : ViewModel() { var passwordEntityId: Int = -1 private val _domainState = MutableStateFlow("") @@ -58,7 +58,7 @@ class ViewPasswordViewModel( fun onDeletePasswordButtonClick() { viewModelScope.launch { try { - val rowsDeleted = controller.deletePassword(passwordEntityId) + controller.deletePassword(passwordEntityId) } catch (_: Exception) { _isErrorState.update { true diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt index e773a039..edb0ab29 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt @@ -1,7 +1,6 @@ package com.jeeldobariya.passcodes.utils import android.content.Context -import android.widget.Toast import com.jeeldobariya.passcodes.database.MasterDatabase import com.jeeldobariya.passcodes.database.Password import com.jeeldobariya.passcodes.database.PasswordsDao diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt index b08a5ea0..560becaf 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -2,7 +2,11 @@ package com.jeeldobariya.passcodes.utils import android.content.Context import android.widget.Toast -import okhttp3.* +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import java.io.IOException object UpdateChecker {