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 -