Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ android {

buildFeatures {
viewBinding = true
buildConfig = true
}
}

Expand All @@ -150,6 +151,9 @@ dependencies {
implementation(libs.room.ktx)
ksp(libs.room.compiler)

implementation(libs.okhttp)
implementation(libs.json)

implementation(libs.coroutines.core)
implementation(libs.coroutines.android)

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="${appIcon}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +30,10 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

lifecycleScope.launch(Dispatchers.IO) {
UpdateChecker.checkVersion(this@MainActivity, BuildConfig.VERSION_NAME)
}

// Add event onclick listener
addOnClickListenerOnButton()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,29 @@ 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
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_LONG
).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
this@PasswordManagerActivity,
getString(R.string.import_failed),
Toast.LENGTH_SHORT
e.message,
Toast.LENGTH_LONG
).show()
}
}
Expand Down Expand Up @@ -136,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")
}

Expand All @@ -146,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")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
26 changes: 19 additions & 7 deletions app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,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"
}

/**
Expand Down Expand Up @@ -149,20 +150,30 @@ 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) {
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
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(
Expand All @@ -174,7 +185,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()
Expand All @@ -184,9 +195,10 @@ class Controller(context: Context) {
importedPasswordCount++
} catch (e: InvalidInputException) {
e.printStackTrace()
failToImportedPasswordCount++
}
}

return importedPasswordCount
return intArrayOf(importedPasswordCount, failToImportedPasswordCount)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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<Release> {
val array = JSONArray(json)
val list = mutableListOf<Release>()
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
}

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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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 val client = OkHttpClient()

fun checkVersion(context: Context, currentVersion: String) {
val appcontext = context.applicationContext
val currentNormalizeVersion = SemVerUtils.normalize(currentVersion)

val request = Request.Builder()
.url(Constant.GITHUB_RELEASE_API_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 == currentNormalizeVersion) {
userReleaseFound = true
if (release.prerelease) {
showToast(
appcontext,
"⚠️ You are using a PRE-RELEASE ($currentNormalizeVersion). Not safe for use! Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})"
)
}
}

if (!release.prerelease) {
if (latestStable == null ||
SemVerUtils.compare(release.tag, latestStable) > 0
) {
latestStable = release.tag
}
}
}

latestStable?.let {
if (SemVerUtils.compare(currentNormalizeVersion, it) < 0) {
showToast(appcontext, "New Update available: $it... Vist our website...")
}
}

if (!userReleaseFound) {
showToast(appcontext, "⚠️ Version ($currentNormalizeVersion) not found on GitHub releases... Join telegram community (${Constant.TELEGRAM_COMMUNITY_URL})")
}
}
})
}

private fun showToast(context: Context, message: String) {
android.os.Handler(context.mainLooper).post {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
}
7 changes: 3 additions & 4 deletions app/src/main/res/layout/activity_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,11 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/clear_all_data_btn"
android:layout_width="wrap_content"
style="@style/Widget.Material3.Button.TonalButton"
android:textColor="?attr/colorOnError"
android:backgroundTint="?attr/colorOnErrorContainer"
android:layout_height="wrap_content"
android:text="@string/clear_all_data_button_text"
android:textSize="12sp"
style="@style/Widget.Material3.Button.OutlinedButton"
android:textColor="?attr/colorError"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/res/layout/activity_view_password.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,16 @@

<com.google.android.material.button.MaterialButton
android:id="@+id/update_password_btn"
style="@style/Widget.Material3.Button.TonalButton"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/update_password_button_text"
android:textSize="14dp" />

<com.google.android.material.button.MaterialButton
android:id="@+id/delete_password_btn"
style="@style/Widget.Material3.Button.TonalButton"
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"
Expand Down
Loading