From 53aa3c69d3b9dc274ce438080426f22931f2e482 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 20:52:56 +0530 Subject: [PATCH 01/17] chore(deps): add okhttp as http cilent --- app/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d148f636..cb0baec5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -150,6 +150,9 @@ dependencies { implementation(libs.room.ktx) ksp(libs.room.compiler) + implementation(libs.okhttp) + implementation(libs.json) + implementation(libs.coroutines.core) implementation(libs.coroutines.android) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01765b0b..94c75d95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,11 @@ [versions] kotlin = "1.9.0" material = "1.12.0" +okhttp = "5.1.0" oss-license = "17.2.1" appcompat = "1.7.0" room = "2.7.2" -# json = "20250517" +json = "20250517" junit = "4.13.2" truth = "1.4.4" androidx-test-ext-junit = "1.2.1" @@ -29,6 +30,8 @@ room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-testing = { module = "androidx.room:room-testing", version.ref = "room" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp"} + coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } @@ -36,7 +39,7 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } # lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } -# json = { module = "org.json:json", version.ref = "json" } +json = { module = "org.json:json", version.ref = "json" } junit = { module = "junit:junit", version.ref = "junit" } truth = { module = "com.google.truth:truth", version.ref = "truth" } From 692869a90b9cd9229759e62edaafb067249f5f7e Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 21:38:14 +0530 Subject: [PATCH 02/17] chore: enable build config --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cb0baec5..3e1d300f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -127,6 +127,7 @@ android { buildFeatures { viewBinding = true + buildConfig = true } } From 76aa4c28e64755e66f1e47e5dae807ebba19bbd6 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 21:38:49 +0530 Subject: [PATCH 03/17] feat: add update checker --- .../jeeldobariya/passcodes/ui/MainActivity.kt | 9 +++ .../passcodes/utils/SemVerUtils.kt | 46 ++++++++++++ .../passcodes/utils/UpdateChecker.kt | 71 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt 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 f1fe9825..5b60ec0d 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt @@ -6,10 +6,15 @@ import android.content.Intent 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 com.jeeldobariya.passcodes.utils.Permissions @@ -25,6 +30,10 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + lifecycleScope.launch(Dispatchers.IO) { + UpdateChecker.checkVersion(this@MainActivity, "v1.0.0") + } + // Add event onclick listener addOnClickListenerOnButton() diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt new file mode 100644 index 00000000..d4cf8ee2 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt @@ -0,0 +1,46 @@ +package com.jeeldobariya.passcodes.utils + +import org.json.JSONArray + +object SemVerUtils { + /** Compare two semantic versions. + * > 0 if v1 > v2, < 0 if v1 < v2, 0 if equal + */ + fun compare(v1: String, v2: String): Int { + val cleanV1 = v1.trimStart('v', 'V') + val cleanV2 = v2.trimStart('v', 'V') + + val parts1 = cleanV1.split(".") + val parts2 = cleanV2.split(".") + + val maxLength = maxOf(parts1.size, parts2.size) + for (i in 0 until maxLength) { + val p1 = parts1.getOrNull(i)?.toIntOrNull() ?: 0 + val p2 = parts2.getOrNull(i)?.toIntOrNull() ?: 0 + if (p1 != p2) return p1 - p2 + } + return 0 + } + + data class Release( + val tag: String, + val prerelease: Boolean, + val draft: Boolean + ) + + fun parseReleases(json: String): List { + val array = JSONArray(json) + val list = mutableListOf() + for (i in 0 until array.length()) { + val obj = array.getJSONObject(i) + list.add( + Release( + tag = obj.getString("tag_name"), + prerelease = obj.getBoolean("prerelease"), + draft = obj.getBoolean("draft") + ) + ) + } + return list + } +} diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt new file mode 100644 index 00000000..9c462fd6 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -0,0 +1,71 @@ +package com.jeeldobariya.passcodes.utils + +import android.content.Context +import android.widget.Toast +import okhttp3.* +import java.io.IOException + +object UpdateChecker { + private const val RELEASES_URL = + "https://api.github.com/repos/JeelDobariya38/Passcodes/releases" + + private val client = OkHttpClient() + + fun checkVersion(context: Context, currentVersion: String) { + val request = Request.Builder() + .url(RELEASES_URL) + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + e.printStackTrace() + } + + override fun onResponse(call: Call, response: Response) { + val body = response.body?.string() ?: return + val releases = SemVerUtils.parseReleases(body) + + var userReleaseFound = false + var latestStable: String? = null + + for (release in releases) { + if (release.draft) continue // ignore drafts + + if (release.tag == currentVersion) { + userReleaseFound = true + if (release.prerelease) { + showToast( + context, + "⚠️ You are using a PRE-RELEASE build ($currentVersion). Not safe for production!" + ) + } + } + + if (!release.prerelease) { + if (latestStable == null || + SemVerUtils.compare(release.tag, latestStable) > 0 + ) { + latestStable = release.tag + } + } + } + + latestStable?.let { + if (SemVerUtils.compare(currentVersion, it) < 0) { + showToast(context, "Update available: $it") + } + } + + if (!userReleaseFound) { + showToast(context, "⚠️ Version ($currentVersion) not found on GitHub releases") + } + } + }) + } + + private fun showToast(context: Context, message: String) { + android.os.Handler(context.mainLooper).post { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } + } +} From bd37173670fdddaf38b091a4e9f0ff97e1180b9c Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 21:39:28 +0530 Subject: [PATCH 04/17] test: add test for semantic version utils --- .../passcodes/utils/SemVerUtilsTest.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt diff --git a/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt b/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt new file mode 100644 index 00000000..9e711b78 --- /dev/null +++ b/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt @@ -0,0 +1,94 @@ +package com.jeeldobariya.passcodes.utils + +import org.junit.Test +import com.google.common.truth.Truth.assertThat + +class SemVerUtilsTest { + + @Test + fun testCompareEqual() { + assertThat(SemVerUtils.compare("1.2.3", "1.2.3")).isEqualTo(0) + assertThat(SemVerUtils.compare("v1.0.0", "v1.0.0")).isEqualTo(0) + } + + @Test + fun testCompareGreater() { + assertThat(SemVerUtils.compare("1.2.10", "1.2.2")).isGreaterThan(0) + assertThat(SemVerUtils.compare("v2.0.0", "v1.9.9")).isGreaterThan(0) + } + + @Test + fun testCompareLess() { + assertThat(SemVerUtils.compare("1.2.2", "v1.2.10")).isLessThan(0) + assertThat(SemVerUtils.compare("v0.9.0", "1.0.0")).isLessThan(0) + } + + @Test + fun testPrefixV() { + assertThat(SemVerUtils.compare("v1.2.10", "v1.2.2")).isGreaterThan(0) + assertThat(SemVerUtils.compare("V2.0.0", "V1.9.9")).isGreaterThan(0) + assertThat(SemVerUtils.compare("1.2.2", "v1.2.10")).isLessThan(0) + assertThat(SemVerUtils.compare("v0.9.0", "1.0.0")).isLessThan(0) + } + + @Test + fun testParseReleases() { + val json = """ + [ + { + "url": "https://api.github.com/repos/JeelDobariya38/Passcodes/releases/240385777", + "assets_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/releases/240385777/assets", + "upload_url": "https://uploads.github.com/repos/JeelDobariya38/Passcodes/releases/240385777/assets{?name,label}", + "html_url": "https://github.com/JeelDobariya38/Passcodes/releases/tag/v1.0.0", + "id": 240385777, + "author": {}, + "node_id": "RE_kwDOMffp084OU_7x", + "tag_name": "v1.0.0", + "target_commitish": "main", + "name": "v1.0.0 - Stable Release", + "draft": false, + "immutable": false, + "prerelease": false, + "created_at": "2025-08-16T17:23:16Z", + "updated_at": "2025-08-17T12:56:19Z", + "published_at": "2025-08-16T18:29:16Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/tarball/v1.0.0", + "zipball_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/zipball/v1.0.0", + "body": "", + "mentions_count": 3 + }, + { + "url": "https://api.github.com/repos/JeelDobariya38/Passcodes/releases/171838408", + "assets_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/releases/171838408/assets", + "upload_url": "https://uploads.github.com/repos/JeelDobariya38/Passcodes/releases/171838408/assets{?name,label}", + "html_url": "https://github.com/JeelDobariya38/Passcodes/releases/tag/v0.1.0", + "id": 171838408, + "author": {}, + "node_id": "RE_kwDOMffp084KPgvI", + "tag_name": "v0.1.0", + "target_commitish": "main", + "name": "v0.1.0 - Alpha Release [Yanked Released]", + "draft": false, + "immutable": false, + "prerelease": true, + "created_at": "2024-08-25T16:13:32Z", + "updated_at": "2025-08-16T16:09:32Z", + "published_at": "2024-08-26T03:51:02Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/tarball/v0.1.0", + "zipball_url": "https://api.github.com/repos/JeelDobariya38/Passcodes/zipball/v0.1.0", + "body": "", + "mentions_count": 2 + } + ] + """.trimIndent() + + val releases = SemVerUtils.parseReleases(json) + + assertThat(releases.size).isEqualTo(2) + assertThat(releases[0].tag).isEqualTo("v1.0.0") + assertThat(releases[0].prerelease).isFalse() + assertThat(releases[1].prerelease).isTrue() + } +} From 78d87162463e74f4a60cdba59146af6a5f748cd2 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 21:40:06 +0530 Subject: [PATCH 05/17] test: remove example test case --- .../passcodes/utils/ExampleTestableCode.kt | 17 -------- .../utils/ExampleTestableCodeTest.kt | 41 ------------------- 2 files changed, 58 deletions(-) delete mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCode.kt delete mode 100644 app/src/test/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCodeTest.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCode.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCode.kt deleted file mode 100644 index 91b65ad9..00000000 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCode.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.jeeldobariya.passcodes.utils - -class ExampleTestableCode { - fun checkStrength(password: String): Int { - if (password.isEmpty()) { - return -1; - } - - val length = password.length; - - if (length < 8) { - return 0; - } else { - return 1; - } - } -} diff --git a/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCodeTest.kt b/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCodeTest.kt deleted file mode 100644 index 95cc7d62..00000000 --- a/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/ExampleTestableCodeTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.jeeldobariya.passcodes.utils - -import org.junit.Test -import org.junit.Before -import com.google.common.truth.Truth.assertThat - -class ExampleTestableCodeTest { - private lateinit var testObj: ExampleTestableCode - - @Before - fun setup() { - testObj = ExampleTestableCode() - } - - @Test - fun `should handle empty passwords`() { - // Given - val password = "" - - // When & Then - assertThat(testObj.checkStrength(password)).isEqualTo(-1) - } - - @Test - fun `should detect short password as weak`() { - // Given - val password = "short" // Less than 8 characters - - // When & Then - assertThat(testObj.checkStrength(password)).isEqualTo(0) - } - - @Test - fun `should detect log password as weak`() { - // Given - val password = "long password" // More than 8 characters - - // When & Then - assertThat(testObj.checkStrength(password)).isEqualTo(1) - } -} From c101560915417079d8a66973d334a03e95dbdc88 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 22:05:45 +0530 Subject: [PATCH 06/17] feat: made the build config version name generalized --- .../jeeldobariya/passcodes/ui/MainActivity.kt | 4 +++- .../jeeldobariya/passcodes/utils/SemVerUtils.kt | 13 +++++++++++++ .../passcodes/utils/SemVerUtilsTest.kt | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) 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 5b60ec0d..d77edb41 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt @@ -15,6 +15,7 @@ import com.jeeldobariya.passcodes.BuildConfig import com.jeeldobariya.passcodes.databinding.ActivityMainBinding import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.UpdateChecker +import com.jeeldobariya.passcodes.utils.SemVerUtils // import com.jeeldobariya.passcodes.utils.Permissions @@ -31,7 +32,8 @@ class MainActivity : AppCompatActivity() { setContentView(binding.root) lifecycleScope.launch(Dispatchers.IO) { - UpdateChecker.checkVersion(this@MainActivity, "v1.0.0") + val currVersion: String = SemVerUtils.normalize(BuildConfig.VERSION_NAME) + UpdateChecker.checkVersion(this@MainActivity, currVersion) } // Add event onclick listener diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt index d4cf8ee2..c323dba9 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtils.kt @@ -43,4 +43,17 @@ object SemVerUtils { } return list } + + fun normalize(tag: String): String { + // Remove 'v' prefix if present + val clean = tag.trimStart('v', 'V') + + // Cut off pre-release (-...) or build metadata (+...) + val cutIndex = clean.indexOfAny(charArrayOf('-', '+')) + return if (cutIndex != -1) { + "v" + clean.substring(0, cutIndex) + } else { + "v$clean" + } + } } diff --git a/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt b/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt index 9e711b78..2ce73484 100644 --- a/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt +++ b/app/src/test/kotlin/com/jeeldobariya/passcodes/utils/SemVerUtilsTest.kt @@ -31,6 +31,23 @@ class SemVerUtilsTest { assertThat(SemVerUtils.compare("v0.9.0", "1.0.0")).isLessThan(0) } + @Test + fun testNormalize() { + assertThat(SemVerUtils.normalize("v1.2.3-beta")).isEqualTo("v1.2.3") + assertThat(SemVerUtils.normalize("1.0.0-alpha.1+001")).isEqualTo("v1.0.0") + assertThat(SemVerUtils.normalize("V2.3.4-rc1")).isEqualTo("v2.3.4") + assertThat(SemVerUtils.normalize("1.5.0")).isEqualTo("v1.5.0") + assertThat(SemVerUtils.normalize("v2.5.0")).isEqualTo("v2.5.0") + assertThat(SemVerUtils.normalize("v1.0.0-Stable-Dev")).isEqualTo("v1.0.0") + + // Note: try some invalid / wired stuff just to test + assertThat(SemVerUtils.normalize("v1.0-Stable-Dev")).isEqualTo("v1.0") + assertThat(SemVerUtils.normalize("v1.0------")).isEqualTo("v1.0") + assertThat(SemVerUtils.normalize("v1.0-abc")).isEqualTo("v1.0") + assertThat(SemVerUtils.normalize("v--1.0-abc")).isEqualTo("v") + assertThat(SemVerUtils.normalize("")).isEqualTo("v") + } + @Test fun testParseReleases() { val json = """ From 8ed5f75e15ee274a0300c85e1417e079dba5f40e Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 22:18:13 +0530 Subject: [PATCH 07/17] refactor: constants like github url --- .../com/jeeldobariya/passcodes/ui/MainActivity.kt | 4 +--- .../com/jeeldobariya/passcodes/utils/Constants.kt | 13 ++++++++----- .../jeeldobariya/passcodes/utils/UpdateChecker.kt | 7 +++---- 3 files changed, 12 insertions(+), 12 deletions(-) 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 d77edb41..8d97690a 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/MainActivity.kt @@ -15,7 +15,6 @@ import com.jeeldobariya.passcodes.BuildConfig import com.jeeldobariya.passcodes.databinding.ActivityMainBinding import com.jeeldobariya.passcodes.utils.CommonUtils import com.jeeldobariya.passcodes.utils.UpdateChecker -import com.jeeldobariya.passcodes.utils.SemVerUtils // import com.jeeldobariya.passcodes.utils.Permissions @@ -32,8 +31,7 @@ class MainActivity : AppCompatActivity() { setContentView(binding.root) lifecycleScope.launch(Dispatchers.IO) { - val currVersion: String = SemVerUtils.normalize(BuildConfig.VERSION_NAME) - UpdateChecker.checkVersion(this@MainActivity, currVersion) + UpdateChecker.checkVersion(this@MainActivity, BuildConfig.VERSION_NAME) } // Add event onclick listener diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt index 5dc959bd..42566233 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt @@ -1,12 +1,15 @@ package com.jeeldobariya.passcodes.utils object Constant { - // Url Constants - const val REPO_URL = "https://github.com/JeelDobariya38/Passcodes" - const val REPORT_BUG_URL = "https://github.com/JeelDobariya38/password-manager/issues/new?template=bug-report.md" - const val RELEASE_NOTE_URL = "https://github.com/JeelDobariya38/Passcodes/blob/main/docs/release-notes.md" - const val SECURITY_GUIDE_URL = "https://github.com/JeelDobariya38/Passcodes/blob/main/docs/security-guide.md" + const val REPOSITORY_SIGNATURE = "JeelDobariya38/Passcodes" + + // URL Constants const val TELEGRAM_COMMUNITY_URL = "https://t.me/passcodescommunity" + const val REPOSITORY_URL = "https://github.com/${REPOSITORY_SIGNATURE}" + const val GITHUB_RELEASE_API_URL = "https://api.github.com/repos/${REPOSITORY_SIGNATURE}/releases" + const val REPORT_BUG_URL = "${REPOSITORY_URL}/issues/new?template=bug-report.md" + const val RELEASE_NOTE_URL = "${REPOSITORY_URL}/blob/main/docs/release-notes.md" + const val SECURITY_GUIDE_URL = "${REPOSITORY_URL}/blob/main/docs/security-guide.md" // Shared Preferences Constants 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 9c462fd6..eda8afdb 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -6,14 +6,13 @@ import okhttp3.* import java.io.IOException object UpdateChecker { - private const val RELEASES_URL = - "https://api.github.com/repos/JeelDobariya38/Passcodes/releases" - private val client = OkHttpClient() fun checkVersion(context: Context, currentVersion: String) { + SemVerUtils.normalize(currentVersion) + val request = Request.Builder() - .url(RELEASES_URL) + .url(Constant.GITHUB_RELEASE_API_URL) .build() client.newCall(request).enqueue(object : Callback { From 1994c1871beeba087c1fb68a7b9d111bd7d7b9ed Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Thu, 28 Aug 2025 22:24:31 +0530 Subject: [PATCH 08/17] refactor: the toast message strings --- .../com/jeeldobariya/passcodes/utils/Constants.kt | 10 +++++----- .../com/jeeldobariya/passcodes/utils/UpdateChecker.kt | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt index 42566233..13be42ff 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Constants.kt @@ -5,11 +5,11 @@ object Constant { // URL Constants const val TELEGRAM_COMMUNITY_URL = "https://t.me/passcodescommunity" - const val REPOSITORY_URL = "https://github.com/${REPOSITORY_SIGNATURE}" - const val GITHUB_RELEASE_API_URL = "https://api.github.com/repos/${REPOSITORY_SIGNATURE}/releases" - const val REPORT_BUG_URL = "${REPOSITORY_URL}/issues/new?template=bug-report.md" - const val RELEASE_NOTE_URL = "${REPOSITORY_URL}/blob/main/docs/release-notes.md" - const val SECURITY_GUIDE_URL = "${REPOSITORY_URL}/blob/main/docs/security-guide.md" + const val REPOSITORY_URL = "https://github.com/$REPOSITORY_SIGNATURE" + const val GITHUB_RELEASE_API_URL = "https://api.github.com/repos/$REPOSITORY_SIGNATURE/releases" + const val REPORT_BUG_URL = "$REPOSITORY_URL/issues/new?template=bug-report.md" + const val RELEASE_NOTE_URL = "$REPOSITORY_URL/blob/main/docs/release-notes.md" + const val SECURITY_GUIDE_URL = "$REPOSITORY_URL/blob/main/docs/security-guide.md" // Shared Preferences Constants 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 eda8afdb..7403ead7 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -35,7 +35,7 @@ object UpdateChecker { if (release.prerelease) { showToast( context, - "⚠️ You are using a PRE-RELEASE build ($currentVersion). Not safe for production!" + "⚠️ You are using a PRE-RELEASE ($currentVersion). Not safe for use! Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})" ) } } @@ -51,12 +51,12 @@ object UpdateChecker { latestStable?.let { if (SemVerUtils.compare(currentVersion, it) < 0) { - showToast(context, "Update available: $it") + showToast(context, "New Update available: $it... Vist our website...") } } if (!userReleaseFound) { - showToast(context, "⚠️ Version ($currentVersion) not found on GitHub releases") + showToast(context, "⚠️ Version ($currentVersion) not found on GitHub releases... Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})") } } }) From 6878c03567935025b1209c3fabbb47fecc5ef19f Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Fri, 29 Aug 2025 10:13:05 +0530 Subject: [PATCH 09/17] fix: the not found error --- .../kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7403ead7..20c96dfc 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -9,7 +9,7 @@ object UpdateChecker { private val client = OkHttpClient() fun checkVersion(context: Context, currentVersion: String) { - SemVerUtils.normalize(currentVersion) + currentVersion = SemVerUtils.normalize(currentVersion) val request = Request.Builder() .url(Constant.GITHUB_RELEASE_API_URL) From 5b6e558f3d06d8d3231155f962cc9dcab0b0cd28 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Fri, 29 Aug 2025 10:17:11 +0530 Subject: [PATCH 10/17] feat: improve button ui --- app/src/main/res/layout/activity_settings.xml | 2 +- app/src/main/res/layout/activity_view_password.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index c0daf6eb..1c694b4d 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -136,7 +136,7 @@ Date: Fri, 29 Aug 2025 10:35:06 +0530 Subject: [PATCH 11/17] refactor: made update checker scaleable --- .../kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt | 1 + 1 file changed, 1 insertion(+) 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 20c96dfc..c10c3773 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -9,6 +9,7 @@ object UpdateChecker { private val client = OkHttpClient() fun checkVersion(context: Context, currentVersion: String) { + context = context.applicationContext currentVersion = SemVerUtils.normalize(currentVersion) val request = Request.Builder() From 1e306727296c2b62ea6985d8a9c10b90d86e02aa Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Fri, 29 Aug 2025 10:39:44 +0530 Subject: [PATCH 12/17] fix: the Val reassign errors --- .../passcodes/utils/UpdateChecker.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 c10c3773..37090715 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/UpdateChecker.kt @@ -9,8 +9,8 @@ object UpdateChecker { private val client = OkHttpClient() fun checkVersion(context: Context, currentVersion: String) { - context = context.applicationContext - currentVersion = SemVerUtils.normalize(currentVersion) + val appcontext = context.applicationContext + val currentNormalizeVersion = SemVerUtils.normalize(currentVersion) val request = Request.Builder() .url(Constant.GITHUB_RELEASE_API_URL) @@ -31,12 +31,12 @@ object UpdateChecker { for (release in releases) { if (release.draft) continue // ignore drafts - if (release.tag == currentVersion) { + if (release.tag == currentNormalizeVersion) { userReleaseFound = true if (release.prerelease) { showToast( - context, - "⚠️ You are using a PRE-RELEASE ($currentVersion). Not safe for use! Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})" + appcontext, + "⚠️ You are using a PRE-RELEASE ($currentNormalizeVersion). Not safe for use! Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})" ) } } @@ -51,13 +51,13 @@ object UpdateChecker { } latestStable?.let { - if (SemVerUtils.compare(currentVersion, it) < 0) { - showToast(context, "New Update available: $it... Vist our website...") + if (SemVerUtils.compare(currentNormalizeVersion, it) < 0) { + showToast(appcontext, "New Update available: $it... Vist our website...") } } if (!userReleaseFound) { - showToast(context, "⚠️ Version ($currentVersion) 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 f39d1a5ee314518f39ba5769fc8d5d3d3bec972d Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Fri, 29 Aug 2025 10:43:27 +0530 Subject: [PATCH 13/17] feat: add a toast for clear data --- .../com/jeeldobariya/passcodes/ui/SettingsActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 53ba7ac2..492d7edd 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt @@ -105,7 +105,11 @@ class SettingsActivity : AppCompatActivity() { } binding.clearAllDataBtn.setOnClickListener { v -> - lifecycleScope.launch { controller.clearAllData() } + lifecycleScope.launch { + controller.clearAllData() + } + + Toast.makeText(this@SettingsActivity, "Delete the user data!!", Toast.LENGTH_SHORT).show() } } } From 86d5100d7bab208220d4870fa462032406c0a5ab Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sat, 30 Aug 2025 09:58:05 +0530 Subject: [PATCH 14/17] feat: fix the colors --- app/src/main/AndroidManifest.xml | 2 ++ app/src/main/res/layout/activity_settings.xml | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eaa3fe1b..21615d35 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + From 7f254e2094038d5ae47e3027c396ae4d1f5aa50e Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sat, 30 Aug 2025 10:44:29 +0530 Subject: [PATCH 15/17] fix: the import error with google passwords --- .../passcodes/ui/PasswordManagerActivity.kt | 20 +++++++++++++------ .../passcodes/utils/Controller.kt | 16 +++++++++++---- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) 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 393f8957..74199a62 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt @@ -56,14 +56,22 @@ class PasswordManagerActivity : AppCompatActivity() { lifecycleScope.launch(Dispatchers.IO) { if (CSVData != null) { try { - val importCount: Int = controller.importDataFromCsvString(CSVData) + val result: IntArray = controller.importDataFromCsvString(CSVData) withContext(Dispatchers.Main) { - Toast.makeText( - this@PasswordManagerActivity, - getString(R.string.import_success, importCount), - Toast.LENGTH_SHORT - ).show() + if (result[1] == 0) { + Toast.makeText( + this@PasswordManagerActivity, + getString(R.string.import_success, result[0]), + Toast.LENGTH_SHORT + ).show() + } else { + Toast.makeText( + this@PasswordManagerActivity, + getString(R.string.import_failed, result[1]), + Toast.LENGTH_SHORT + ).show() + } } } catch (e: Exception) { withContext(Dispatchers.Main) { 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 e264fb71..acbee10e 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt @@ -1,6 +1,7 @@ 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 @@ -149,7 +150,7 @@ class Controller(context: Context) { return CSV_HEADER + "\n" + rows } - suspend fun importDataFromCsvString(csvString: String): Int { + suspend fun importDataFromCsvString(csvString: String): IntArray { val lines = csvString.lines().filter { it.isNotBlank() } if (lines.isEmpty() || lines[0] != CSV_HEADER) { @@ -157,12 +158,18 @@ class Controller(context: Context) { } var importedPasswordCount = 0 + var failToImportedPasswordCount = 0 lines.drop(1).forEach { line -> val cols = line.split(",") + /* NOTE: this need to be done, because our app not allow empty domain. */ + 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 = cols[0].trim()) + val password: Password? = passwordsDao.getPasswordByUsernameAndDomain(username = cols[2].trim(), domain = chosenDomain) if (password != null) { updatePassword( @@ -174,7 +181,7 @@ class Controller(context: Context) { ) } else { savePasswordEntity( - domain = cols[0].trim(), + domain = chosenDomain, username = cols[2].trim(), password = cols[3].trim(), notes = cols[4].trim() @@ -184,9 +191,10 @@ class Controller(context: Context) { importedPasswordCount++ } catch (e: InvalidInputException) { e.printStackTrace() + failToImportedPasswordCount++ } } - return importedPasswordCount + return intArrayOf(importedPasswordCount, failToImportedPasswordCount) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c50c2a6..bdaa8bf7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,7 +87,7 @@ Action discarded. Something Went Wrong: Invalid ID!! Imported %1$d passwords - Failed to import CSV + Failed to import %1$d passwords Passwords exported From 27353aad481e79e4a80501fac886cfa43f4fbdd3 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 31 Aug 2025 09:06:44 +0530 Subject: [PATCH 16/17] feat: make google passwords compatible with import/export --- .../passcodes/ui/PasswordManagerActivity.kt | 24 +++++++++---------- .../passcodes/utils/Controller.kt | 10 +++++--- 2 files changed, 19 insertions(+), 15 deletions(-) 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 74199a62..4c4556fc 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/PasswordManagerActivity.kt @@ -59,17 +59,17 @@ class PasswordManagerActivity : AppCompatActivity() { val result: IntArray = controller.importDataFromCsvString(CSVData) withContext(Dispatchers.Main) { - if (result[1] == 0) { - Toast.makeText( - this@PasswordManagerActivity, - getString(R.string.import_success, result[0]), - Toast.LENGTH_SHORT - ).show() - } else { + Toast.makeText( + this@PasswordManagerActivity, + getString(R.string.import_success, result[0]), + Toast.LENGTH_LONG + ).show() + + if (result[1] != 0) { Toast.makeText( this@PasswordManagerActivity, getString(R.string.import_failed, result[1]), - Toast.LENGTH_SHORT + Toast.LENGTH_LONG ).show() } } @@ -77,8 +77,8 @@ class PasswordManagerActivity : AppCompatActivity() { withContext(Dispatchers.Main) { Toast.makeText( this@PasswordManagerActivity, - getString(R.string.import_failed), - Toast.LENGTH_SHORT + e.message, + Toast.LENGTH_LONG ).show() } } @@ -144,7 +144,7 @@ class PasswordManagerActivity : AppCompatActivity() { private fun exportCsvFilePicker() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = "text/comma-separated-values" + type = "text/csv" putExtra(Intent.EXTRA_TITLE, "passwords.csv") } @@ -154,7 +154,7 @@ class PasswordManagerActivity : AppCompatActivity() { private fun importCsvFilePicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = "text/comma-separated-values" + type = "text/csv" putExtra(Intent.EXTRA_TITLE, "passwords.csv") } 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 acbee10e..84fd0c9b 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt @@ -23,7 +23,7 @@ class Controller(context: Context) { } companion object { - const val CSV_HEADER = "name,url,username,password,notes" + const val CSV_HEADER = "name,url,username,password,note" } /** @@ -153,8 +153,12 @@ class Controller(context: Context) { suspend fun importDataFromCsvString(csvString: String): IntArray { val lines = csvString.lines().filter { it.isNotBlank() } - if (lines.isEmpty() || lines[0] != CSV_HEADER) { - throw InvalidImportFormat() + 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}") } var importedPasswordCount = 0 From 5b1e313d21d9bd170810a9028ad1c5f975bd29ce Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 31 Aug 2025 09:14:27 +0530 Subject: [PATCH 17/17] feat: improve ui consistency --- app/src/main/res/layout/activity_view_password.xml | 1 - app/src/main/res/values/strings.xml | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/activity_view_password.xml b/app/src/main/res/layout/activity_view_password.xml index ac29d91e..17f7650e 100644 --- a/app/src/main/res/layout/activity_view_password.xml +++ b/app/src/main/res/layout/activity_view_password.xml @@ -92,7 +92,6 @@ android:id="@+id/delete_password_btn" style="@style/Widget.Material3.Button.OutlinedButton" android:textColor="?attr/colorOnError" - android:backgroundTint="?attr/colorOnErrorContainer" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/delete_password_button_text" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bdaa8bf7..644b2089 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,9 +31,9 @@ Load Password Update Password Delete Password - Import Password - Export Password - Clear All Data + Import G-Password + Export G-Password + Clear Data Check Security Settings Toggle Theme @@ -67,7 +67,7 @@ Password: Notes: Created At: - Updated At: + Last Updated: Permission granted