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 128e43e0..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,10 +94,15 @@ 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()
+ }
}
}
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..d41bfe2f 100644
--- a/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt
+++ b/app/src/main/kotlin/com/jeeldobariya/passcodes/ui/ViewPasswordActivity.kt
@@ -13,8 +13,10 @@ 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.DateTimeUtils
import com.jeeldobariya.passcodes.utils.PasswordNotFoundException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -50,6 +52,8 @@ class ViewPasswordActivity : AppCompatActivity() {
controller = Controller(this)
+ binding.copyPasswordBtn.isEnabled = FeatureFlagManager.get(this).latestFeaturesEnabled
+
// Add event onclick listener
addOnClickListenerOnButton()
@@ -62,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"
+ }
+ }
+ }
+}
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,10 +82,40 @@
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" />
+
+
+
+
+
+
+
+
+
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 @@
- @android:color/transparent
- @android:color/transparent
- false
- - true
- - true
+ - false
+ - false
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e8fddd99..42183db6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -39,6 +39,7 @@
Language:
Contributor:
Themes:
+ Preview Features:
About Us
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 104af446..003b7221 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -186,5 +186,4 @@
- true
- true
-