diff --git a/app/build.gradle b/app/build.gradle
index eaca6a22..ebb456fb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,8 +20,8 @@ android {
applicationId appPackageId
minSdkVersion 22
targetSdkVersion 30
- versionCode 1308
- versionName "1.3.8"
+ versionCode 1309
+ versionName "1.3.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -79,7 +79,7 @@ dependencies {
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
-
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
@@ -112,15 +112,23 @@ dependencies {
implementation "androidx.preference:preference-ktx:1.1.1"
+ implementation "androidx.work:work-runtime-ktx:2.6.0"
+
+
+
//Androidx Security
implementation "androidx.security:security-crypto:1.1.0-alpha03"
- implementation 'com.google.code.gson:gson:2.8.6'
+ implementation 'com.google.code.gson:gson:2.8.8'
// dependency injection
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
+ implementation("androidx.hilt:hilt-work:1.0.0")
+ // When using Kotlin.
+ kapt("androidx.hilt:hilt-compiler:1.0.0")
+
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5b0b8449..681f30f6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
-
+
@@ -34,6 +34,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt b/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt
index 01528bfc..e0d31c77 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/MyApplication.kt
@@ -1,7 +1,10 @@
package com.yogeshpaliyal.keypass
import android.app.Application
+import androidx.hilt.work.HiltWorkerFactory
+import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
+import javax.inject.Inject
/*
* @author Yogesh Paliyal
@@ -10,14 +13,25 @@ import dagger.hilt.android.HiltAndroidApp
* created on 22-01-2021 22:41
*/
@HiltAndroidApp
-class MyApplication : Application() {
+class MyApplication : Application(), Configuration.Provider {
companion object {
lateinit var instance: MyApplication
}
+ @Inject
+ lateinit var workerFactory: HiltWorkerFactory
+
override fun onCreate() {
super.onCreate()
instance = this
}
+
+ override fun getWorkManagerConfiguration(): Configuration {
+ return Configuration.Builder()
+ .setMinimumLoggingLevel(android.util.Log.INFO)
+ .setWorkerFactory(workerFactory)
+ .build()
+ }
+
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt b/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt
index 87b5ab62..b4c7201b 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/db/DbDao.kt
@@ -1,10 +1,7 @@
package com.yogeshpaliyal.keypass.db
import androidx.lifecycle.LiveData
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
+import androidx.room.*
import com.yogeshpaliyal.keypass.data.AccountModel
import kotlinx.coroutines.flow.Flow
@@ -41,4 +38,7 @@ abstract class DbDao {
@Query("DELETE from account WHERE id = :id")
abstract fun deleteAccount(id: Long?)
+
+ @Delete
+ abstract fun deleteAccount(accountModel: AccountModel)
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt
index bf32d3e0..cb725907 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupActivity.kt
@@ -10,6 +10,7 @@ import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yogeshpaliyal.keypass.AppDatabase
@@ -80,6 +81,7 @@ class BackupActivity : AppCompatActivity() {
if (it.canUserAccessBackupDirectory(sp)) {
val selectedDirectory = Uri.parse(getBackupDirectory(sp))
backup(selectedDirectory)
+
}
}
}
@@ -98,20 +100,60 @@ class BackupActivity : AppCompatActivity() {
}
updateItems()
}
+ getString(R.string.settings_override_auto_backup) -> {
+ sp.apply {
+ setOverrideAutoBackup(overrideAutoBackup().not())
+ }
+ updateItems()
+ }
}
return super.onPreferenceTreeClick(preference)
}
+ fun backup(selectedDirectory: Uri){
+ lifecycleScope.launch {
+ context.backupAccounts(sp, appDb,selectedDirectory)?.let { keyPair ->
+ if (keyPair.first) {
+ val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
+ binding.txtCode.text = getOrCreateBackupKey(sp).second
+ binding.txtCode.setOnClickListener {
+ val clipboard =
+ ContextCompat.getSystemService(
+ requireContext(),
+ ClipboardManager::class.java
+ )
+ val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
+ clipboard?.setPrimaryClip(clip)
+ Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
+ }
+ MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
+ .setPositiveButton(
+ getString(R.string.yes)
+ ) { dialog, which ->
+ dialog?.dismiss()
+ }.show()
+ } else {
+ Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ updateItems()
+ }
+
private fun updateItems() {
val isBackupEnabled = context.canUserAccessBackupDirectory(sp)
val isAutoBackupEnabled = sp.isAutoBackupEnabled()
findPreference(getString(R.string.settings_start_backup))?.isVisible = isBackupEnabled.not()
+ findPreference(getString(R.string.settings_stop_backup))?.isVisible = isBackupEnabled
findPreference(getString(R.string.settings_auto_backup))?.isVisible = isBackupEnabled
findPreference(getString(R.string.settings_auto_backup))?.summary = if(isAutoBackupEnabled) getString(R.string.enabled) else getString(R.string.disabled)
+ findPreference(getString(R.string.settings_cat_auto_backup))?.isVisible = isBackupEnabled && isAutoBackupEnabled
+
+ findPreference(getString(R.string.settings_override_auto_backup))?.summary = if(sp.overrideAutoBackup()) getString(R.string.enabled) else getString(R.string.disabled)
findPreference(getString(R.string.settings_create_backup))?.isVisible = isBackupEnabled
findPreference(getString(R.string.settings_create_backup))?.summary = getString(R.string.last_backup_date, getBackupTime(sp).formatCalendar("dd MMM yyyy hh:mm aa"))
@@ -156,50 +198,7 @@ class BackupActivity : AppCompatActivity() {
}
}
- private fun backup(selectedDirectory: Uri) {
- val keyPair = getOrCreateBackupKey(sp)
-
- val tempFile = DocumentFile.fromTreeUri(requireContext(), selectedDirectory)?.createFile(
- "*/*",
- "key_pass_backup_${System.currentTimeMillis()}.keypass"
- )
-
- lifecycleScope.launch {
- context?.contentResolver?.let {
- appDb.createBackup(
- keyPair.second,
- it,
- tempFile?.uri
- )
- setBackupTime(sp, System.currentTimeMillis())
- if (keyPair.first) {
- val binding = LayoutBackupKeypharseBinding.inflate(layoutInflater)
- binding.txtCode.text = getOrCreateBackupKey(sp).second
- binding.txtCode.setOnClickListener {
- val clipboard =
- ContextCompat.getSystemService(
- requireContext(),
- ClipboardManager::class.java
- )
- val clip = ClipData.newPlainText("KeyPass", binding.txtCode.text)
- clipboard?.setPrimaryClip(clip)
- Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
- }
- MaterialAlertDialogBuilder(requireContext()).setView(binding.root)
- .setPositiveButton(
- getString(R.string.yes)
- ) { dialog, which ->
- dialog?.dismiss()
- }.show()
- } else {
- Toast.makeText(context, getString(R.string.backup_completed), Toast.LENGTH_SHORT).show()
- }
- }
-
- updateItems()
- }
- }
private fun changeBackupFolder() {
startBackup()
@@ -213,6 +212,10 @@ class BackupActivity : AppCompatActivity() {
clearBackupKey(sp)
setBackupDirectory(sp, "")
setBackupTime(sp, -1)
+ sp.apply {
+ setOverrideAutoBackup(false)
+ setAutoBackupEnabled(false)
+ }
updateItems()
}
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt
index d2d83272..ba64df2f 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailActivity.kt
@@ -7,17 +7,11 @@ import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
-import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.databinding.FragmentDetailBinding
import com.yogeshpaliyal.keypass.utils.PasswordGenerator
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import javax.inject.Inject
/*
* @author Yogesh Paliyal
@@ -30,9 +24,6 @@ class DetailActivity : AppCompatActivity() {
lateinit var binding: FragmentDetailBinding
- @Inject
- lateinit var appDb: AppDatabase
-
companion object {
private const val ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"
@@ -90,14 +81,8 @@ class DetailActivity : AppCompatActivity() {
}
binding.btnSave.setOnClickListener {
- lifecycleScope.launch(Dispatchers.IO) {
- val model = mViewModel.accountModel.value
- if (model != null) {
- appDb.getDao().insertOrUpdateAccount(model)
- }
- withContext(Dispatchers.Main) {
- onBackPressed()
- }
+ mViewModel.insertOrUpdate {
+ onBackPressed()
}
}
}
@@ -110,11 +95,9 @@ class DetailActivity : AppCompatActivity() {
getString(R.string.delete)
) { dialog, which ->
dialog?.dismiss()
- lifecycleScope.launch(Dispatchers.IO) {
- if (accountId > 0L) {
- appDb.getDao().deleteAccount(accountId)
- }
- withContext(Dispatchers.Main) {
+
+ if (accountId > 0L) {
+ mViewModel.deleteAccount {
onBackPressed()
}
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt
index b8d4fcc5..8134b203 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/DetailViewModel.kt
@@ -1,14 +1,18 @@
package com.yogeshpaliyal.keypass.ui.detail
import android.app.Application
+import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.keypass.AppDatabase
import com.yogeshpaliyal.keypass.data.AccountModel
+import com.yogeshpaliyal.keypass.worker.executeAutoBackup
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
/*
@@ -18,16 +22,44 @@ import javax.inject.Inject
* created on 31-01-2021 11:52
*/
@HiltViewModel
-class DetailViewModel @Inject constructor(application: Application, val appDb: AppDatabase) : AndroidViewModel(application) {
+class DetailViewModel @Inject constructor(val app: Application, val appDb: AppDatabase,val sp: SharedPreferences) : AndroidViewModel(app) {
- val accountModel by lazy { MutableLiveData() }
+ private val _accountModel by lazy { MutableLiveData() }
+ val accountModel : LiveData = _accountModel
fun loadAccount(accountId: Long?) {
-
viewModelScope.launch(Dispatchers.IO) {
- accountModel.postValue(
+ _accountModel.postValue(
appDb.getDao().getAccount(accountId) ?: AccountModel()
)
}
}
+
+ fun deleteAccount(onExecCompleted : ()->Unit){
+ viewModelScope.launch {
+ accountModel.value?.let {
+ withContext(Dispatchers.IO) {
+ appDb.getDao().deleteAccount(it)
+ }
+ autoBackup()
+ onExecCompleted()
+ }
+ }
+ }
+
+ fun insertOrUpdate(onExecCompleted : ()->Unit){
+ viewModelScope.launch {
+ accountModel.value?.let {
+ withContext(Dispatchers.IO) {
+ appDb.getDao().insertOrUpdateAccount(it)
+ autoBackup()
+ }
+ }
+ onExecCompleted()
+ }
+ }
+
+ private fun autoBackup(){
+ app.executeAutoBackup(sp)
+ }
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt
index cda3731d..e3f9e539 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/BackupUtils.kt
@@ -5,6 +5,8 @@ import android.content.SharedPreferences
import android.net.Uri
import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
+import com.yogeshpaliyal.keypass.AppDatabase
+import com.yogeshpaliyal.keypass.db_helper.createBackup
import java.security.SecureRandom
/*
@@ -21,9 +23,9 @@ fun getRandomString(sizeOfRandomString: Int): String {
val sb = StringBuilder(sizeOfRandomString)
for (i in 0 until sizeOfRandomString) sb.append(
ALLOWED_CHARACTERS[
- random.nextInt(
- ALLOWED_CHARACTERS.length
- )
+ random.nextInt(
+ ALLOWED_CHARACTERS.length
+ )
]
)
return sb.toString()
@@ -36,6 +38,42 @@ fun Context?.canUserAccessBackupDirectory(sp: SharedPreferences): Boolean {
return backupDirectory != null && backupDirectory.exists() && backupDirectory.canRead() && backupDirectory.canWrite()
}
+
+/**
+ * @return Pair (Boolean to check if backup is for first time, is backup is for first time show user alert to save encryption key)
+ * Second Value contains the encryption key
+ */
+suspend fun Context?.backupAccounts(
+ sp: SharedPreferences,
+ appDb: AppDatabase,
+ selectedDirectory: Uri, fileName: String? = null
+): Pair? {
+
+ this ?: return null
+
+ val keyPair = getOrCreateBackupKey(sp)
+
+ val fileName = (fileName ?: "key_pass_backup_${System.currentTimeMillis()}")+".keypass"
+
+
+ val directory = DocumentFile.fromTreeUri(this, selectedDirectory)
+ var docFile = directory?.findFile(fileName)
+ if (docFile == null)
+ docFile = DocumentFile.fromTreeUri(this, selectedDirectory)?.createFile(
+ "*/*",
+ fileName)
+
+
+ val response = appDb.createBackup(
+ keyPair.second,
+ contentResolver,
+ docFile?.uri
+ )
+ setBackupTime(sp, System.currentTimeMillis())
+
+ return keyPair
+}
+
private fun getUri(string: String?): Uri? {
val uri = string
return if (TextUtils.isEmpty(uri)) {
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt
index 7aa7f10a..1cf21a25 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/utils/SharedPreferenceUtils.kt
@@ -55,6 +55,16 @@ fun SharedPreferences?.isAutoBackupEnabled(): Boolean {
return this?.getBoolean(AUTO_BACKUP, false) ?: false
}
+fun SharedPreferences?.overrideAutoBackup(): Boolean {
+ return this?.getBoolean(OVERRIDE_AUTO_BACKUP, false) ?: false
+}
+
+fun SharedPreferences?.setOverrideAutoBackup(value: Boolean) {
+ this?.edit {
+ putBoolean(OVERRIDE_AUTO_BACKUP, value)
+ }
+}
+
fun SharedPreferences?.setAutoBackupEnabled(value: Boolean) {
this?.edit {
putBoolean(AUTO_BACKUP, value)
@@ -69,3 +79,4 @@ private const val BACKUP_KEY = "backup_key"
private const val BACKUP_DIRECTORY = "backup_directory"
private const val BACKUP_DATE_TIME = "backup_date_time"
private const val AUTO_BACKUP = "auto_backup"
+private const val OVERRIDE_AUTO_BACKUP = "override_auto_backup"
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/worker/AutoBackupWorker.kt b/app/src/main/java/com/yogeshpaliyal/keypass/worker/AutoBackupWorker.kt
new file mode 100644
index 00000000..e3df6674
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/worker/AutoBackupWorker.kt
@@ -0,0 +1,42 @@
+package com.yogeshpaliyal.keypass.worker
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.net.Uri
+import androidx.hilt.work.HiltWorker
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.yogeshpaliyal.keypass.AppDatabase
+import com.yogeshpaliyal.keypass.utils.backupAccounts
+import com.yogeshpaliyal.keypass.utils.canUserAccessBackupDirectory
+import com.yogeshpaliyal.keypass.utils.getBackupDirectory
+import com.yogeshpaliyal.keypass.utils.overrideAutoBackup
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@HiltWorker
+class AutoBackupWorker @AssistedInject constructor(
+ @Assisted val appContext: Context, @Assisted params: WorkerParameters,
+ val appDatabase: AppDatabase,
+ val sp: SharedPreferences
+) :
+ CoroutineWorker(appContext, params) {
+ override suspend fun doWork(): Result {
+ return withContext(Dispatchers.IO) {
+
+ if (appContext.canUserAccessBackupDirectory(sp)) {
+ val selectedDirectory = Uri.parse(getBackupDirectory(sp))
+ appContext.backupAccounts(
+ sp,
+ appDatabase,
+ selectedDirectory,
+ if (sp.overrideAutoBackup()) "key_pass_auto_backup" else null
+ )
+ }
+
+ Result.success()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/worker/MyWorkManager.kt b/app/src/main/java/com/yogeshpaliyal/keypass/worker/MyWorkManager.kt
new file mode 100644
index 00000000..190ca623
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/worker/MyWorkManager.kt
@@ -0,0 +1,19 @@
+package com.yogeshpaliyal.keypass.worker
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.yogeshpaliyal.keypass.utils.isAutoBackupEnabled
+
+fun Context?.executeAutoBackup(sp: SharedPreferences){
+ this ?: return
+
+ if (sp.isAutoBackupEnabled()) {
+ val work = OneTimeWorkRequestBuilder().build()
+
+ WorkManager.getInstance(this.applicationContext)
+ .enqueueUniqueWork("AutoBackupWorker", ExistingWorkPolicy.KEEP, work)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-hi/string.xml b/app/src/main/res/values-hi/string.xml
index b2d8a95b..79ff8634 100644
--- a/app/src/main/res/values-hi/string.xml
+++ b/app/src/main/res/values-hi/string.xml
@@ -59,4 +59,9 @@
टैग अल्पविराम से अलग (वैकल्पिक)
वेबसाइट यूआरएल (वैकल्पिक)
नोट्स (वैकल्पिक)
+
+ ऑटो बैकअप
+ अक्षम
+ सक्रिय
+ ऑटो बैकअप फ़ाइल को ओवरराइड करें
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 03e4c15f..20490283 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -59,4 +59,9 @@
标签(选填)
网站(选填)
附注(选填)
+
+ 自动备份
+ 已禁用
+ 启用
+ 覆盖自动备份文件
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fb656ae8..dbaaca86 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -62,4 +62,5 @@
Auto Backup
Disabled
Enabled
+ Override Auto Backup File
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_no_translate.xml b/app/src/main/res/values/strings_no_translate.xml
index de83d1bc..74cfd201 100644
--- a/app/src/main/res/values/strings_no_translate.xml
+++ b/app/src/main/res/values/strings_no_translate.xml
@@ -10,5 +10,7 @@
backup_folder
stop_backup
auto_backup
+ cat_auto_backup
+ override_auto_backup
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_preferences.xml b/app/src/main/res/xml/backup_preferences.xml
index f22c7864..b2ce7ac7 100644
--- a/app/src/main/res/xml/backup_preferences.xml
+++ b/app/src/main/res/xml/backup_preferences.xml
@@ -3,39 +3,52 @@
-
+
+ app:title="@string/turn_on_backup" />
+ tools:summary="Last backup : 7m" />
+ tools:summary="@string/backup" />
+ tools:summary="@string/disabled" />
+
+
+
+
+
+
+
+
+ app:summary="@string/verify_keyphrase_message"
+ app:title="@string/verify_keyphrase" />
+ app:key="@string/settings_stop_backup"
+ app:title="@string/turn_off_backup" />
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1308.txt b/fastlane/metadata/android/en-US/changelogs/1308.txt
new file mode 100644
index 00000000..9d124829
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1308.txt
@@ -0,0 +1 @@
+Fixed crash for some devices when creating backup
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1309.txt b/fastlane/metadata/android/en-US/changelogs/1309.txt
new file mode 100644
index 00000000..d1ccbff1
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1309.txt
@@ -0,0 +1,2 @@
+New Feature -> Auto backup whenever there is changes in accounts :D
+Added Chinese (Simplified)
\ No newline at end of file
diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US
index 9d124829..d1ccbff1 100644
--- a/whatsnew/whatsnew-en-US
+++ b/whatsnew/whatsnew-en-US
@@ -1 +1,2 @@
-Fixed crash for some devices when creating backup
\ No newline at end of file
+New Feature -> Auto backup whenever there is changes in accounts :D
+Added Chinese (Simplified)
\ No newline at end of file