From ff47105f3fcaeffe53cf8bc0d922d8e8fa334d36 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 10:50:35 +0530 Subject: [PATCH 1/6] fix: light theme status bar issue in safe theme --- app/src/main/res/values-night/themes.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index f2abb6ba..cb79506a 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -184,8 +184,8 @@ @android:color/transparent @android:color/transparent false - true - true + false + false From 56ed426eec1c9cdc2c2250678b4563e553b208d3 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 10:55:16 +0530 Subject: [PATCH 2/6] feat: implement a latest feature toggle --- .../passcodes/ui/SettingsActivity.kt | 4 +++ app/src/main/res/layout/activity_settings.xml | 30 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 13 ++++++++ 4 files changed, 48 insertions(+) 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 128e43e0..5de5fc5d 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt @@ -95,5 +95,9 @@ class SettingsActivity : AppCompatActivity() { Toast.makeText(this@SettingsActivity, getString(R.string.restart_app_require), Toast.LENGTH_SHORT).show() } + + binding.switchLatestFeatures.setOnCheckedChangeListener { _, isChecked -> + Toast.makeText(this@SettingsActivity, getString(R.string.future_feat_clause) + isChecked.toString(), Toast.LENGTH_SHORT).show() + } } } diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 2018c7c2..80dcc3cc 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -88,4 +88,34 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8fddd99..a3e66fd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,4 +92,5 @@ Copying sensitive data like passwords to clipboard is not so good for security!!! Confirm Discard + Latest Feature: diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 104af446..2ff3d907 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -187,4 +187,17 @@ true + + From 4efb8d0edd8a6268a02cf3434b9ebabf281528a9 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 11:12:42 +0530 Subject: [PATCH 3/6] feat: implement feature flagging --- .../passcodes/flags/FeatureFlagManager.kt | 27 +++++++++++++++++++ .../passcodes/ui/SettingsActivity.kt | 7 ++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt new file mode 100644 index 00000000..e4dcf6c1 --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/flags/FeatureFlagManager.kt @@ -0,0 +1,27 @@ +package com.jeeldobariya.passcodes.flags + +import android.content.Context +import androidx.core.content.edit + +class FeatureFlagManager private constructor(context: Context) { + private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + var latestFeaturesEnabled: Boolean + get() = prefs.getBoolean(KEY_LATEST_FEATURES, false) + set(value) { prefs.edit { putBoolean(KEY_LATEST_FEATURES, value) } } + + companion object { + private const val PREFS_NAME = "feature_flags" + private const val KEY_LATEST_FEATURES = "latest_features_enabled" + + @Volatile private var INSTANCE: FeatureFlagManager? = null + + fun get(context: Context): FeatureFlagManager { + return INSTANCE ?: synchronized(this) { + val instance = FeatureFlagManager(context.applicationContext) + INSTANCE = instance + instance + } + } + } +} 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 5de5fc5d..54e0a6a7 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/SettingsActivity.kt @@ -13,6 +13,8 @@ import android.view.LayoutInflater import com.jeeldobariya.passcodes.R import com.jeeldobariya.passcodes.databinding.ActivitySettingsBinding +import com.jeeldobariya.passcodes.flags.FeatureFlagManager +import androidx.core.content.edit class SettingsActivity : AppCompatActivity() { @@ -44,6 +46,8 @@ class SettingsActivity : AppCompatActivity() { setInitialLangSelection() + binding.switchLatestFeatures.isChecked = FeatureFlagManager.get(this).latestFeaturesEnabled + // Add event onclick listener addOnClickListenerOnButton() @@ -90,13 +94,14 @@ class SettingsActivity : AppCompatActivity() { val newThemeStyle = THEMES[nextIndex] // Save the new theme and restart the application to apply it - sharedPrefs.edit().putInt(THEME_KEY, newThemeStyle).apply() + sharedPrefs.edit { putInt(THEME_KEY, newThemeStyle) } recreate() 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() } } From f95a78901883bcefc1d4ff2b513bc8b91b818f90 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 11:18:30 +0530 Subject: [PATCH 4/6] feat: copy password preview features --- .../com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt | 3 +++ 1 file changed, 3 insertions(+) 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 c0ae1f1d..a41c90f6 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt @@ -13,6 +13,7 @@ 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.Controller import com.jeeldobariya.passcodes.utils.DatabaseOperationException import com.jeeldobariya.passcodes.utils.PasswordNotFoundException @@ -50,6 +51,8 @@ class ViewPasswordActivity : AppCompatActivity() { controller = Controller(this) + binding.copyPasswordBtn.isEnabled = FeatureFlagManager.get(this).latestFeaturesEnabled + // Add event onclick listener addOnClickListenerOnButton() From c3fe77fab9f5c9cded48c5817779d1dd48268fe2 Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 11:39:15 +0530 Subject: [PATCH 5/6] feat: improve toggle button and refactor code --- .../res/layout/activity_password_manager.xml | 4 ++-- app/src/main/res/layout/activity_settings.xml | 20 +++++++++---------- .../res/layout/activity_view_password.xml | 11 ++++------ app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/themes.xml | 14 ------------- 5 files changed, 17 insertions(+), 34 deletions(-) diff --git a/app/src/main/res/layout/activity_password_manager.xml b/app/src/main/res/layout/activity_password_manager.xml index 00b4d3f4..6067a5f0 100644 --- a/app/src/main/res/layout/activity_password_manager.xml +++ b/app/src/main/res/layout/activity_password_manager.xml @@ -4,7 +4,7 @@ android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" - tools:context=".ui.PasswordManager" + tools:context=".ui.PasswordManagerActivity" android:padding="4sp" > - @@ -58,7 +58,7 @@ android:id="@+id/theme_card" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" app:cardCornerRadius="16dp"> @@ -82,7 +82,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/toggle_theme_button_text" - android:textSize="14dp" + android:textSize="12sp" android:layout_gravity="right|center_vertical" /> @@ -92,29 +92,29 @@ android:id="@+id/latest_features_card" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" app:cardCornerRadius="16dp"> + android:gravity="center" + android:padding="16dp"> - - diff --git a/app/src/main/res/layout/activity_view_password.xml b/app/src/main/res/layout/activity_view_password.xml index 0035efdd..f1231c0c 100644 --- a/app/src/main/res/layout/activity_view_password.xml +++ b/app/src/main/res/layout/activity_view_password.xml @@ -74,8 +74,7 @@ - Language: Contributor: Themes: + Preview Features: About Us @@ -92,5 +93,4 @@ Copying sensitive data like passwords to clipboard is not so good for security!!! Confirm Discard - Latest Feature: diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2ff3d907..003b7221 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -186,18 +186,4 @@ true true - - - From be034ea869efc038426fba69a9695528310b9a7c Mon Sep 17 00:00:00 2001 From: Jeel Dobariya Date: Sun, 24 Aug 2025 12:13:20 +0530 Subject: [PATCH 6/6] feat: improve updatedat date ui --- .../passcodes/ui/ViewPasswordActivity.kt | 5 +- .../passcodes/utils/Controller.kt | 7 +-- .../passcodes/utils/DateTimeUtils.kt | 55 +++++++++++++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/com/jeeldobariya/passcodes/utils/DateTimeUtils.kt 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 a41c90f6..d41bfe2f 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt @@ -16,6 +16,7 @@ import com.jeeldobariya.passcodes.databinding.ActivityViewPasswordBinding import com.jeeldobariya.passcodes.flags.FeatureFlagManager 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 @@ -65,11 +66,13 @@ 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)} ${passwordEntity.updatedAt}" + binding.tvUpdatedAt.text = "${getString(R.string.updatedat_prefix)} ${lastUpdatedAt}" } } catch (e: PasswordNotFoundException) { 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 e2c20064..4e8a4a85 100644 --- a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/Controller.kt @@ -4,9 +4,6 @@ import android.content.Context import com.jeeldobariya.passcodes.database.MasterDatabase import com.jeeldobariya.passcodes.database.Password import com.jeeldobariya.passcodes.database.PasswordsDao -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale import kotlinx.coroutines.flow.Flow class InvalidInputException(message: String = "Input parameters cannot be blank.") : Exception(message) @@ -34,7 +31,7 @@ class Controller(context: Context) { throw InvalidInputException() } - val currentTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + val currentTimestamp = DateTimeUtils.getCurrDateTime() val newPassword = Password( domain = domain, username = username, @@ -106,7 +103,7 @@ class Controller(context: Context) { val existingPassword = passwordsDao.getPasswordById(id) ?: throw PasswordNotFoundException("Password with ID $id not found for update.") - val updatedTimestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + val updatedTimestamp = DateTimeUtils.getCurrDateTime() val passwordToUpdate = existingPassword.copy( domain = domain, username = username, diff --git a/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/DateTimeUtils.kt b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/DateTimeUtils.kt new file mode 100644 index 00000000..2619d4aa --- /dev/null +++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/utils/DateTimeUtils.kt @@ -0,0 +1,55 @@ +package com.jeeldobariya.passcodes.utils + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit + +class DateTimeUtils { + companion object { + /** Returns current date-time in `yyyy-MM-dd HH:mm:ss` format */ + fun getCurrDateTime(): String { + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + .format(Date()) + } + + /** Returns current date in `yyyy-MM-dd` format */ + fun getCurrDate(): String { + return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + .format(Date()) + } + + /** Returns current time in `HH:mm:ss` format */ + fun getCurrTime(): String { + return SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + .format(Date()) + } + + /** Parse a String into a Date with a given pattern */ + fun parseDate(dateStr: String, pattern: String = "yyyy-MM-dd HH:mm:ss"): Date? { + return try { + SimpleDateFormat(pattern, Locale.getDefault()).parse(dateStr) + } catch (e: Exception) { + null + } + } + + fun getRelativeDays(dateStr: String, pattern: String = "yyyy-MM-dd"): String { + return try { + val parsedDate = parseDate(dateStr, pattern) ?: return "Invalid date" + + val now = System.currentTimeMillis() + val diff = parsedDate.time - now + val days = TimeUnit.MILLISECONDS.toDays(diff) + + when { + days == 0L -> "today" + days > 0L -> "in $days day${if (days > 1) "s" else ""}" + else -> "${-days} day${if (days < -1) "s" else ""} ago" + } + } catch (e: Exception) { + "Invalid date format" + } + } + } +}