Merge pull request #2209 from MihanEntalpo/automatic-backup-to-public-folder

Automatic backup to public folder
dev
Alinson S. Xavier 2 weeks ago committed by GitHub
commit 2b24759d6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -32,6 +32,7 @@ rules = "1.6.1"
shadow = "8.1.1"
sqliteJdbc = "3.45.1.0"
uiautomator = "2.3.0"
documentfile = "1.0.1"
[libraries]
annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
@ -73,6 +74,7 @@ opencsv = { group = "com.opencsv", name = "opencsv", version.ref = "opencsv" }
rules = { group = "androidx.test", name = "rules", version.ref = "rules" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbc" }
uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
[bundles]
androidTest = [

@ -106,6 +106,7 @@ dependencies {
implementation(libs.legacy.preference.v14)
implementation(libs.legacy.support.v4)
implementation(libs.material)
implementation(libs.documentfile)
implementation(libs.opencsv)
implementation(libs.konfetti.xml)
implementation(project(":uhabits-core"))

@ -33,6 +33,8 @@ import org.isoron.uhabits.acceptance.steps.clearDownloadFolder
import org.isoron.uhabits.acceptance.steps.copyBackupToDownloadFolder
import org.isoron.uhabits.acceptance.steps.exportFullBackup
import org.isoron.uhabits.acceptance.steps.importBackupFromDownloadFolder
import org.isoron.uhabits.acceptance.steps.selectPublicBackupFolder
import org.isoron.uhabits.acceptance.steps.verifyBackupInDownloadFolder
import org.junit.Test
@LargeTest
@ -51,4 +53,14 @@ class BackupTest : BaseUserInterfaceTest() {
importBackupFromDownloadFolder()
verifyDisplaysText("Wake up early")
}
@Test
fun shouldExportBackupToPublicFolder() {
launchApp()
clearDownloadFolder()
clearBackupFolder()
selectPublicBackupFolder()
exportFullBackup()
verifyBackupInDownloadFolder()
}
}

@ -19,8 +19,10 @@
package org.isoron.uhabits.acceptance.steps
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.SystemClock.sleep
import androidx.preference.PreferenceManager
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import org.isoron.uhabits.BaseUserInterfaceTest.Companion.device
@ -28,6 +30,8 @@ import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText
import org.isoron.uhabits.acceptance.steps.CommonSteps.pressBack
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.SETTINGS
import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu
import org.junit.Assert.assertTrue
import java.io.File
const val BACKUP_FOLDER = "/sdcard/Android/data/org.isoron.uhabits/files/Backups/"
const val DOWNLOAD_FOLDER = "/sdcard/Download/"
@ -41,6 +45,7 @@ fun exportFullBackup() {
fun clearDownloadFolder() {
device.executeShellCommand("rm -rf /sdcard/Download")
device.executeShellCommand("mkdir /sdcard/Download")
}
fun clearBackupFolder() {
@ -52,6 +57,13 @@ fun copyBackupToDownloadFolder() {
device.executeShellCommand("chown root $DOWNLOAD_FOLDER")
}
fun selectPublicBackupFolder() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val uri = Uri.fromFile(File(DOWNLOAD_FOLDER))
prefs.edit().putString("publicBackupFolder", uri.toString()).commit()
}
fun importBackupFromDownloadFolder() {
clickMenu(SETTINGS)
clickText("Import data")
@ -93,6 +105,11 @@ fun importBackupFromDownloadFolder() {
}
}
fun verifyBackupInDownloadFolder() {
val listing = device.executeShellCommand("ls $DOWNLOAD_FOLDER")
assertTrue(listing.contains("Loop Habits Backup"))
}
fun openLauncher() {
device.pressHome()
device.waitForIdle()

@ -24,6 +24,8 @@ import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
@ -60,10 +62,21 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == RINGTONE_REQUEST_CODE) {
ringtoneManager!!.update(data)
updateRingtoneDescription()
return
when (requestCode) {
RINGTONE_REQUEST_CODE -> {
ringtoneManager!!.update(data)
updateRingtoneDescription()
return
}
PUBLIC_BACKUP_REQUEST_CODE -> {
val uri = data?.data ?: return
val flags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
requireContext().contentResolver.takePersistableUriPermission(uri, flags)
sharedPrefs?.edit()?.putString("publicBackupFolder", uri.toString())?.apply()
updatePublicBackupFolderSummary()
return
}
}
super.onActivityResult(requestCode, resultCode, data)
}
@ -127,6 +140,16 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
activity?.startActivitySafely(intent)
return true
}
"publicBackupFolder" -> {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
)
startActivityForResult(intent, PUBLIC_BACKUP_REQUEST_CODE)
return true
}
}
return super.onPreferenceTreeClick(preference)
}
@ -141,6 +164,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
devCategory.isVisible = false
}
updateWeekdayPreference()
updatePublicBackupFolderSummary()
findPreference("reminderSound").isVisible = false
}
@ -205,7 +229,39 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
ringtonePreference.summary = ringtoneName
}
private fun updatePublicBackupFolderSummary() {
val pref = findPreference("publicBackupFolder")
val uriString = sharedPrefs?.getString("publicBackupFolder", null)
if (uriString == null) {
pref.summary = getString(R.string.no_public_backup_folder_selected)
return
}
val uri = Uri.parse(uriString)
val path = fullPathFor(uri)
pref.summary = path ?: uriString
}
private fun fullPathFor(uri: Uri): String? {
return when (uri.scheme) {
"content" -> {
val docId = DocumentsContract.getTreeDocumentId(uri)
val (type, rel) = docId.split(":", limit = 2).let {
it[0] to it.getOrElse(1) { "" }
}
val base = if (type.equals("primary", true)) {
Environment.getExternalStorageDirectory().absolutePath
} else {
"/storage/$type"
}
if (rel.isEmpty()) base else "$base/$rel"
}
"file" -> java.io.File(uri.path!!).absolutePath
else -> null
}
}
companion object {
private const val RINGTONE_REQUEST_CODE = 1
private const val PUBLIC_BACKUP_REQUEST_CODE = 2
}
}

@ -20,7 +20,10 @@
package org.isoron.uhabits.database
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.utils.DatabaseUtils
@ -28,37 +31,69 @@ import java.io.File
class AutoBackup(private val context: Context) {
private val basedir = AndroidDirFinder(context).getFilesDir("Backups")!!
private val backupPattern = Regex("^Loop Habits Backup .+\\.db$")
fun run(keep: Int = 5) {
Log.i("AutoBackup", "Starting automatic backups...")
val files = listBackupFiles()
var newestTimestamp = 0L
if (files.isNotEmpty()) {
newestTimestamp = files.last().lastModified()
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val uriString = prefs.getString("publicBackupFolder", null)
if (uriString != null) {
val uri = Uri.parse(uriString)
val dir = if (uri.scheme == "content") {
DocumentFile.fromTreeUri(context, uri)
} else {
DocumentFile.fromFile(File(uri.path!!))
}
if (dir != null) {
runInPublicDir(dir, keep)
return
}
}
val basedir = AndroidDirFinder(context).getFilesDir("Backups") ?: return
runInPrivateDir(basedir, keep)
}
private fun runInPrivateDir(dir: File, keep: Int) {
val files = dir.listFiles()?.toMutableList() ?: mutableListOf()
files.sortBy { it.lastModified() }
val newestTimestamp = files.lastOrNull()?.lastModified() ?: 0L
removeOldestPrivate(files, keep)
val now = DateUtils.getLocalTime()
if (now - newestTimestamp > DateUtils.DAY_LENGTH) {
DatabaseUtils.saveDatabaseCopy(context, dir)
} else {
Log.i("AutoBackup", "Fresh backup found (timestamp=$newestTimestamp)")
}
}
private fun runInPublicDir(dir: DocumentFile, keep: Int) {
val files = dir.listFiles()
.filter { it.isFile && it.name?.matches(backupPattern) == true }
.sortedBy { it.lastModified() }
val newestTimestamp = files.lastOrNull()?.lastModified() ?: 0L
removeOldestPublic(files, keep)
val now = DateUtils.getLocalTime()
removeOldest(files, keep)
if (now - newestTimestamp > DateUtils.DAY_LENGTH) {
DatabaseUtils.saveDatabaseCopy(context, basedir)
DatabaseUtils.saveDatabaseCopy(context, dir)
} else {
Log.i("AutoBackup", "Fresh backup found (timestamp=$newestTimestamp)")
}
}
private fun removeOldest(files: ArrayList<File>, keep: Int) {
files.sortBy { -it.lastModified() }
for (k in keep until files.size) {
Log.i("AutoBackup", "Removing ${files[k]}")
files[k].delete()
private fun removeOldestPrivate(files: List<File>, keep: Int) {
for (k in 0 until (files.size - keep)) {
val file = files[k]
Log.i("AutoBackup", "Removing $file")
file.delete()
}
}
private fun listBackupFiles(): ArrayList<File> {
val files = ArrayList<File>()
for (path in basedir.list()!!) {
files.add(File("${basedir.path}/$path"))
private fun removeOldestPublic(files: List<DocumentFile>, keep: Int) {
for (k in 0 until (files.size - keep)) {
val file = files[k]
Log.i("AutoBackup", "Removing ${file.uri}")
file.delete()
}
return files
}
}

@ -19,10 +19,14 @@
package org.isoron.uhabits.tasks
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.core.tasks.Task
import org.isoron.uhabits.inject.AppContext
import org.isoron.uhabits.utils.DatabaseUtils.saveDatabaseCopy
import java.io.File
import java.io.IOException
class ExportDBTask(
@ -34,8 +38,26 @@ class ExportDBTask(
override fun doInBackground() {
filename = null
filename = try {
val dir = system.getFilesDir("Backups") ?: return
saveDatabaseCopy(context, dir)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val uriString = prefs.getString("publicBackupFolder", null)
if (uriString != null) {
// if public backup folder is selected, use it for backup
val uri = Uri.parse(uriString)
val dir = if (uri.scheme == "content") {
DocumentFile.fromTreeUri(context, uri)
} else {
DocumentFile.fromFile(File(uri.path!!))
}
if (dir != null) {
saveDatabaseCopy(context, dir)
} else {
null
}
} else {
// if public backup folder is unset, use default system folder to backup
val dir = system.getFilesDir("Backups") ?: return
saveDatabaseCopy(context, dir)
}
} catch (e: IOException) {
throw RuntimeException(e)
}

@ -21,6 +21,7 @@ package org.isoron.uhabits.utils
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import org.isoron.uhabits.HabitsApplication.Companion.isTestMode
import org.isoron.uhabits.HabitsDatabaseOpener
import org.isoron.uhabits.core.DATABASE_FILENAME
@ -28,6 +29,7 @@ import org.isoron.uhabits.core.DATABASE_VERSION
import org.isoron.uhabits.core.utils.DateFormats.Companion.getBackupDateFormat
import org.isoron.uhabits.core.utils.DateUtils.Companion.getLocalTime
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.text.SimpleDateFormat
@ -69,6 +71,23 @@ object DatabaseUtils {
return dbCopy.absolutePath
}
@JvmStatic
@Throws(IOException::class)
fun saveDatabaseCopy(context: Context, dir: DocumentFile): String {
val dateFormat: SimpleDateFormat = getBackupDateFormat()
val date = dateFormat.format(getLocalTime())
val file = dir.createFile("application/octet-stream", "Loop Habits Backup $date.db")
?: throw IOException("Unable to create backup file")
Log.i("DatabaseUtils", "Writing: ${file.uri}")
val db = getDatabaseFile(context)
FileInputStream(db).use { input ->
context.contentResolver.openOutputStream(file.uri)?.use { output ->
input.copyTo(output)
}
}
return file.uri.toString()
}
fun openDatabase(): SQLiteDatabase {
checkNotNull(opener)
return opener!!.writableDatabase

@ -38,4 +38,6 @@
<string name="interval_8_hour">8 ure</string>
<string name="interval_24_hour">24 ure</string>
<string name="settings">Instellings</string>
<string name="select_public_backup_folder">Kies openbare rugsteunmap</string>
<string name="no_public_backup_folder_selected">Geen vouer gekies nie</string>
</resources>

@ -242,4 +242,6 @@
<string name="activity_not_found">لم يتم العثور على تطبيق لإتمام هذا الإجراء</string>
<string name="pref_midnight_delay_title">تمديد اليوم بضع ساعات بعد منتصف الليل</string>
<string name="pref_midnight_delay_description">انتظر حتى 3:00 صباحاً لعرض يوم جديد. مفيد إذا كنت عادة تذهب إلى السكون بعد منتصف الليل. يتطلب إعادة تشغيل التطبيق.</string>
<string name="select_public_backup_folder">اختر مجلد النسخ الاحتياطي العام</string>
<string name="no_public_backup_folder_selected">لم يتم اختيار مجلد</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Не е намерено приложение, което да поддържа това действие</string>
<string name="pref_midnight_delay_title">Удължаване на деня с няколко часа след полунощ</string>
<string name="pref_midnight_delay_description">Изчакване до 3:00 часа сутринта за показване на нов ден. Полезно, ако обичайно си лягате да спите след полунощ. Изисква рестартиране на приложението.</string>
<string name="select_public_backup_folder">Изберете публична папка за резервни копия</string>
<string name="no_public_backup_folder_selected">Не е избрана папка</string>
</resources>

@ -209,4 +209,6 @@
<string name="activity_not_found">No s\'ha trobat cap aplicació per a gestionar aquesta acció</string>
<string name="pref_midnight_delay_title">Ampliar el dia unes hores després de la mitjanit</string>
<string name="pref_midnight_delay_description">Esperar fins a les 3:00 per mostrar un nou dia. Útil si normalment vas a dormir després de la mitjanit. Requereix el reinici de l\'aplicació.</string>
<string name="select_public_backup_folder">Seleccioneu la carpeta pública de còpia de seguretat</string>
<string name="no_public_backup_folder_selected">No s\'ha seleccionat cap carpeta</string>
</resources>

@ -230,4 +230,6 @@
<string name="activity_not_found">Nenalezen program podporující tento krok</string>
<string name="pref_midnight_delay_title">Prodloužit den o pár hodin po půlnoci</string>
<string name="pref_midnight_delay_description">Počkat do tří ráno pro zobrazení nového dne. Užitečné, pokud chodíte spát po půlnoci. Vyžaduje restartování aplikace. </string>
<string name="select_public_backup_folder">Vyberte veřejnou složku zálohy</string>
<string name="no_public_backup_folder_selected">Nebyla vybrána žádná složka</string>
</resources>

@ -221,4 +221,6 @@
<string name="activity_not_found">Ingen app fundet til at understøtte denne handling</string>
<string name="pref_midnight_delay_title">Forlæng dagen et par timer efter midnat</string>
<string name="pref_midnight_delay_description">Vent til 3:00 for at vise en ny dag. Nyttigt, hvis du typisk går i seng efter midnat. Kræver genstart af app.</string>
<string name="select_public_backup_folder">Vælg offentlig sikkerhedskopimappe</string>
<string name="no_public_backup_folder_selected">Ingen mappe valgt</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Für diese Aktion wurde keine App gefunden.</string>
<string name="pref_midnight_delay_title">Verlängere den Tag um ein paar Stunden nach Mitternacht</string>
<string name="pref_midnight_delay_description">Bis 3:00 Uhr warten, bevor ein neuer Tag angezeigt wird. Nützlich, wenn du normalerweise nach Mitternacht schlafen gehst. Benötigt einen Neustart der App.</string>
<string name="select_public_backup_folder">Öffentlichen Backup-Ordner auswählen</string>
<string name="no_public_backup_folder_selected">Kein Ordner ausgewählt</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Δεν βρέθηκε εφαρμογή για την υποστήριξη αυτής της ενέργειας</string>
<string name="pref_midnight_delay_title">Επεκτείνετε την ημέρα λίγες ώρες μετά τα μεσάνυχτα</string>
<string name="pref_midnight_delay_description">Περιμένετε μέχρι τις 3:00 πμ για να εμφανιστεί μια νέα μέρα. Χρήσιμο αν συνήθως πάτε για ύπνο μετά τα μεσάνυχτα. Απαιτεί επανεκκίνηση της εφαρμογής.</string>
<string name="select_public_backup_folder">Επιλέξτε δημόσιο φάκελο αντιγράφων ασφαλείας</string>
<string name="no_public_backup_folder_selected">Δεν επιλέχθηκε φάκελος</string>
</resources>

@ -151,4 +151,6 @@
<string name="pref_view_privacy">Vidu privatecan politikon</string>
<string name="view_all_contributors">Rigardi ĉiujn kontribuantojn&#8230;</string>
<string name="database">Datumbazo</string>
<string name="select_public_backup_folder">Elektu publikan sekurkopi-dosierujon</string>
<string name="no_public_backup_folder_selected">Neniu dosierujo elektita</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">No se encontró ninguna aplicación que admita esta acción</string>
<string name="pref_midnight_delay_title">Ampliar día unas horas después de medianoche</string>
<string name="pref_midnight_delay_description">Esperar hasta las 3:00 AM para mostrar un nuevo día. Útil si normalmente vas a dormir después de medianoche. Requiere reiniciar la aplicación.</string>
<string name="select_public_backup_folder">Seleccionar carpeta de copia de seguridad pública</string>
<string name="no_public_backup_folder_selected">Ninguna carpeta seleccionada</string>
</resources>

@ -216,4 +216,6 @@
<string name="activity_not_found">Ez da aurkitu akzio hau gauzatu dezakeen aplikaziorik</string>
<string name="pref_midnight_delay_title">Luzatu eguna gauerdia osteko ordu batzuetara</string>
<string name="pref_midnight_delay_description">Itxaron goizeko 3:00ak arte egun berri bat erakusteko. Erabilgarria normalean gauerdia pasata lotara joaten bazara. Aplikazioa berrabiarazi behar da.</string>
<string name="select_public_backup_folder">Hautatu babeskopia publikoaren karpeta</string>
<string name="no_public_backup_folder_selected">Ez da karpetarik hautatu</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">هیچ برنامه‌ای برای انجام این کار یافت نشد.</string>
<string name="pref_midnight_delay_title">تمدید کردن روز تا چند ساعت بعد از نیمه‌شب</string>
<string name="pref_midnight_delay_description">تا ساعت ۳ نیمه‌شب برای نمایش یک روز جدید صبر می‌کند. اگر معمولاً بعد از نیمه‌شب می‌خوابید، برای شما می‌تواند مفید باشد. نیازمند راه‌اندازی مجدد برنامه است.</string>
<string name="select_public_backup_folder">انتخاب پوشهٔ پشتیبان‌گیری عمومی</string>
<string name="no_public_backup_folder_selected">هیچ پوشه‌ای انتخاب نشده است</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Tälle toiminnolle ei löytynyt yhtään sovellusta</string>
<string name="pref_midnight_delay_title">Pidennä päivää muutama tunti keskiyön jälkeen</string>
<string name="pref_midnight_delay_description">Odota kello 3.00 asti ennen uuden päivän näyttämistä. Hyödyllinen jos menet nukkumaan yleensä keskiyön jälkeen. Vaatii sovelluksen uudelleenkäynnistyksen.</string>
<string name="select_public_backup_folder">Valitse julkinen varmuuskopiokansio</string>
<string name="no_public_backup_folder_selected">Kansiota ei valittu</string>
</resources>

@ -219,4 +219,6 @@ Combien de pages avez-vous lu ?</string>
<string name="activity_not_found">Aucune application trouvée pour faire cette action</string>
<string name="pref_midnight_delay_title">Prolonger les jours de quelques heures après minuit </string>
<string name="pref_midnight_delay_description">Attendre jusqu\'à trois heures du matin pour changer de jour. Très utile si vous avez l\'habitude d\'aller dormir après minuit. (Redémarrage nécessaire)</string>
<string name="select_public_backup_folder">Sélectionner le dossier de sauvegarde public</string>
<string name="no_public_backup_folder_selected">Aucun dossier sélectionné</string>
</resources>

@ -17,4 +17,6 @@
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> <string name="select_public_backup_folder">જાહેર બેકઅપ ફોલ્ડર પસંદ કરો</string>
<string name="no_public_backup_folder_selected">કોઈ ફોલ્ડર પસંદ કરાયેલ નથી</string>
</resources>

@ -247,4 +247,6 @@
<string name="activity_not_found">इस कार्रवाई को संभालने के लिए कोई ऐप नहीं मिला</string>
<string name="pref_midnight_delay_title">मध्यरात्रि से कुछ घंटे बाद तक दिन बढ़ाएँ</string>
<string name="pref_midnight_delay_description">नया दिन दिखाने के लिए सुबह 3:00 बजे तक प्रतीक्षा करें। उपयोगी अगर आप आमतौर पर आधी रात के बाद सोने जाते हैं। ऐप पुनरारंभ की आवश्यकता होगी।</string>
<string name="select_public_backup_folder">सार्वजनिक बैकअप फ़ोल्डर चुनें</string>
<string name="no_public_backup_folder_selected">कोई फ़ोल्डर चयनित नहीं है</string>
</resources>

@ -198,4 +198,6 @@
<string name="no_habits">Navike nisu pronađene</string>
<string name="no_numerical_habits">Mjerljive navike nisu pronađene</string>
<string name="no_boolean_habits">Da/Ne navike nisu pronađene</string>
<string name="select_public_backup_folder">Odaberite javnu mapu sigurnosne kopije</string>
<string name="no_public_backup_folder_selected">Nije odabrana nijedna mapa</string>
</resources>

@ -223,4 +223,6 @@
<string name="activity_not_found">Ezen művelet elvégzéséhez nem található alkalmazás.</string>
<string name="pref_midnight_delay_title">A nap meghosszabbítása éjfél után néhány órával</string>
<string name="pref_midnight_delay_description">Várjon hajnali 3 -ig, hogy új napot mutasson. Hasznos, ha általában éjfél után fekszik le. Az alkalmazás újraindítását igényli.</string>
<string name="select_public_backup_folder">Nyilvános biztonsági mentési mappa kiválasztása</string>
<string name="no_public_backup_folder_selected">Nincs mappa kiválasztva</string>
</resources>

@ -82,4 +82,6 @@
<string name="x_times_per_month">Ամսական %d անգամ</string>
<string name="color">Գույն</string>
<string name="today">Այսօր</string>
<string name="select_public_backup_folder">Ընտրեք հանրային պահուստային թղթապանակը</string>
<string name="no_public_backup_folder_selected">Թղթապանակ չի ընտրվել</string>
</resources>

@ -221,4 +221,6 @@
<string name="activity_not_found">Aplikasi untuk mendukung tindakan ini tidak ditemukan</string>
<string name="pref_midnight_delay_title">Perpanjang hari beberapa jam setelah tengah malam</string>
<string name="pref_midnight_delay_description">Tunggu sampai jam 3 pagi untuk menampilkan hari yang baru. Berguna jika kamu biasanya tidur setelah tengah malam. Aplikasi perlu dimulai ulang.</string>
<string name="select_public_backup_folder">Pilih folder cadangan publik</string>
<string name="no_public_backup_folder_selected">Tidak ada folder yang dipilih</string>
</resources>

@ -170,4 +170,6 @@
<string name="decrement">Lækka</string>
<string name="pref_midnight_delay_title">Lengja daginn nokkrar klukkustundir yfir miðnætti</string>
<string name="pref_midnight_delay_description">Bíða til klukkan 03:00 til að sýna nýjan dag. Gagnlegt ef þú ferð að sofa eftir miðnætti. Krefst endurræsingar forritsins.</string>
<string name="select_public_backup_folder">Veldu opinbera öryggisafritunarmöppu</string>
<string name="no_public_backup_folder_selected">Engin mappa valin</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Nessuna app disponibile per gestire questa azione</string>
<string name="pref_midnight_delay_title">Prolunga il giorno di alcune ore dopo la mezzanotte</string>
<string name="pref_midnight_delay_description">Attendere fino alle 3:00 per mostrare il nuovo giorno. Utile se solitamente vai a dormire dopo mezzanotte. È necessario riavviare l\'app.</string>
<string name="select_public_backup_folder">Seleziona cartella di backup pubblica</string>
<string name="no_public_backup_folder_selected">Nessuna cartella selezionata</string>
</resources>

@ -230,4 +230,6 @@
<string name="activity_not_found">לא נמצא יישום שתומך בפעולה זו</string>
<string name="pref_midnight_delay_title">הארכת היום בכמה שעות לאחר החצות</string>
<string name="pref_midnight_delay_description">המתנה עד 3:00 לפני הצגת יום חדש. שימושי אם לרוב הולכים לישון אחרי חצות. דורש הפעלה מחדש של היישום.</string>
<string name="select_public_backup_folder">בחר תיקיית גיבוי ציבורית</string>
<string name="no_public_backup_folder_selected">לא נבחרה תיקיה</string>
</resources>

@ -212,4 +212,6 @@
<string name="activity_not_found">この操作を行うアプリが見つかりませんでした。</string>
<string name="pref_midnight_delay_title">一日の終わりを午前0時から数時間延長する</string>
<string name="pref_midnight_delay_description">一日の始まりを午前3時にします。よく午前0時以降に就寝する場合に役立ちます。アプリの再起動が必要です。</string>
<string name="select_public_backup_folder">公開バックアップフォルダを選択</string>
<string name="no_public_backup_folder_selected">フォルダが選択されていません</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">ვერ მოიძებნა აპლიკაცია ამ ოპერაციის განსახორციელებლად</string>
<string name="pref_midnight_delay_title">ახალი დღის დასაწყისის გადატანა ნაშუაღამევის რამდენიმე საათისთვის</string>
<string name="pref_midnight_delay_description">ახალი დღის 03:00 საათზე დაწყება. ხელსაყრელია თუ ზოგადად შუაღამის შემდეგ იძინებ. საჭიროებს აპლიკაციის გადატვირთვას.</string>
<string name="select_public_backup_folder">აირჩიეთ საჯარო სარეზერვო საქაღალდე</string>
<string name="no_public_backup_folder_selected">საქაღალდე არჩეული არ არის</string>
</resources>

@ -212,4 +212,6 @@
<string name="activity_not_found">이 작업을 지원하는 앱을 찾을 수 없습니다.</string>
<string name="pref_midnight_delay_title">하루를 자정 이후까지 연장합니다.</string>
<string name="pref_midnight_delay_description">오전 3시를 기점으로 하루가 변경됩니다. 자정 이후 잠드는 경우 유용합니다. 변경시 앱 재시작 후 적용됩니다.</string>
<string name="select_public_backup_folder">공용 백업 폴더 선택</string>
<string name="no_public_backup_folder_selected">선택된 폴더가 없습니다</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">ഈ പ്രവർത്തനത്തെ പിന്തുണയ്ക്കുന്ന ഒരു ആപ്പും കണ്ടെത്തിയില്ല</string>
<string name="pref_midnight_delay_title">അർദ്ധരാത്രി കഴിഞ്ഞ് കുറച്ച് മണിക്കൂറുകൾ പകൽ നീട്ടുക</string>
<string name="pref_midnight_delay_description">ഒരു പുതിയ ദിവസം കാണിക്കാൻ 3:00 AM വരെ കാത്തിരിക്കുക. നിങ്ങൾ സാധാരണയായി അർദ്ധരാത്രിക്ക് ശേഷം ഉറങ്ങാൻ പോകുകയാണെങ്കിൽ ഉപയോഗപ്രദമാണ്. ആപ്പ് പുനരാരംഭിക്കേണ്ടതുണ്ട്.</string>
<string name="select_public_backup_folder">പൊതു ബാക്കപ്പ് ഫോൾഡർ തിരഞ്ഞെടുക്കുക</string>
<string name="no_public_backup_folder_selected">ഫോൾഡർ ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Er is geen app gevonden om deze actie uit te voeren.</string>
<string name="pref_midnight_delay_title">Verleng de dag tot een paar uur na middernacht</string>
<string name="pref_midnight_delay_description">Wacht tot 3:00 uur om een nieuwe dag te beginnen. Handig als je normaal gesproken na middernacht gaat slapen. Dit vereist het opnieuw opstarten van de app.</string>
<string name="select_public_backup_folder">Openbare back-upmap selecteren</string>
<string name="no_public_backup_folder_selected">Geen map geselecteerd</string>
</resources>

@ -136,4 +136,6 @@
<string name="export">Eksportér</string>
<string name="question">Spørsmål</string>
<string name="x_times_per_y_days">%d ganger på %d dager</string>
<string name="select_public_backup_folder">Velg offentlig sikkerhetskopimappe</string>
<string name="no_public_backup_folder_selected">Ingen mappe valgt</string>
</resources>

@ -230,4 +230,6 @@
<string name="activity_not_found">Nie znaleziono aplikacji obsługującej to działanie</string>
<string name="pref_midnight_delay_title">Przedłuż dzień o kilka godzin po północy</string>
<string name="pref_midnight_delay_description">Poczekaj do 3:00, aby pokazać nowy dzień. Przydatne, jeśli zwykle kładziesz się spać po północy. Wymaga ponownego uruchomienia aplikacji.</string>
<string name="select_public_backup_folder">Wybierz publiczny folder kopii zapasowych</string>
<string name="no_public_backup_folder_selected">Nie wybrano folderu</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Nenhum app encontrado para executar esta ação</string>
<string name="pref_midnight_delay_title">Prolongar dia algumas horas depois da meia-noite</string>
<string name="pref_midnight_delay_description">Espere até às 3:00 para mostrar um novo dia. Útil se você costuma dormir depois da meia-noite. Requer reinicialização do aplicativo.</string>
<string name="select_public_backup_folder">Selecionar pasta pública de backup</string>
<string name="no_public_backup_folder_selected">Nenhuma pasta selecionada</string>
</resources>

@ -178,4 +178,6 @@
<string name="decrement">Decrementar</string>
<string name="you_are_now_a_developer">Agora é um programador!</string>
<string name="activity_not_found">Nenhuma aplicação foi encontrada para oferecer suporte a esta ação</string>
<string name="select_public_backup_folder">Selecionar pasta pública de cópia de segurança</string>
<string name="no_public_backup_folder_selected">Nenhuma pasta selecionada</string>
</resources>

@ -193,4 +193,6 @@
<string name="activity_not_found">Nu a fost găsită nicio aplicație care să efectueze această acțiune.</string>
<string name="pref_midnight_delay_title">Extinde ziua câteva ore după miezul nopții</string>
<string name="pref_midnight_delay_description">Așteaptă până la ora 3:00 dimineața pentru a arăta o nouă zi. O funcție utilă dacă în mod normal obișnuiești să mergi la somn după miezul nopții. Necesită repornirea aplicației.</string>
<string name="select_public_backup_folder">Selectează dosarul public pentru backup</string>
<string name="no_public_backup_folder_selected">Niciun dosar selectat</string>
</resources>

@ -135,6 +135,8 @@
<string name="import_data_summary">Поддерживает полные резервные копии, экспортированные из этого приложения, а также файлы, сгенерированные приложениями Tickmate, HabitBull и Rewire. Подробнее смотрите в FAQ.</string>
<string name="export_as_csv_summary">Генерирует файлы, которые можно открыть в ПО для работы с таблицами (таком как Microsoft Excel или OpenOffice Calc). Этот файл нельзя импортировать обратно.</string>
<string name="export_full_backup_summary">Генерирует файл, который содержит все ваши данные. Этот файл можно импортировать обратно.</string>
<string name="select_public_backup_folder">Выбрать публичную папку для резервных копий</string>
<string name="no_public_backup_folder_selected">Папка не выбрана</string>
<string name="bug_report_failed">Ошибка генерации отчёта об ошибке.</string>
<string name="generate_bug_report">Сгенерировать отчёт об ошибке</string>
<string name="troubleshooting">Устранение неполадок</string>

@ -230,4 +230,6 @@
<string name="activity_not_found">Nenašla sa žiadna aplikácia podporujúca túto akciu</string>
<string name="pref_midnight_delay_title">Predĺžte deň o niekoľko hodín po polnoci</string>
<string name="pref_midnight_delay_description">Na zobrazenie nového dňa počkajte do 3:00. Užitočné, ak zvyčajne chodíte spať po polnoci. Vyžaduje reštart aplikácie.</string>
<string name="select_public_backup_folder">Vybrať verejný priečinok zálohy</string>
<string name="no_public_backup_folder_selected">Nie je vybratý žiadny priečinok</string>
</resources>

@ -230,4 +230,6 @@
<string name="activity_not_found">Najdena ni bila nobena aplikacija, ki bi podpirala to dejanje</string>
<string name="pref_midnight_delay_title">Podaljšajte dan nekaj ur čez polnoč</string>
<string name="pref_midnight_delay_description">Počakajte do 3.00, da prikažete nov dan. Uporabno, če greste običajno spat po polnoči. Zahteva ponovni zagon aplikacije.</string>
<string name="select_public_backup_folder">Izberite javno mapo za varnostne kopije</string>
<string name="no_public_backup_folder_selected">Mapa ni izbrana</string>
</resources>

@ -217,4 +217,6 @@
<string name="activity_not_found">Nema aplikacije koja podržava ovu radnju</string>
<string name="pref_midnight_delay_title">Produžite dan nekoliko sati iza ponoći</string>
<string name="pref_midnight_delay_description">Čeka do 03:00 da pokaže novi dan. Korisno ako obično idete na spavanje posle ponoći. Zahteva ponovno pokretanje.</string>
<string name="select_public_backup_folder">Izaberite javni folder za rezervnu kopiju</string>
<string name="no_public_backup_folder_selected">Nijedan folder nije izabran</string>
</resources>

@ -224,4 +224,6 @@
<string name="activity_not_found">Нема апликације која подржава ову радњу</string>
<string name="pref_midnight_delay_title">Продужите дан неколико сати иза поноћи</string>
<string name="pref_midnight_delay_description">Чека до 03:00 да покаже нови дан. Корисно ако обично идете на спавање после поноћи. Захтева поновно покретање.</string>
<string name="select_public_backup_folder">Изаберите јавну фасциклу за резервну копију</string>
<string name="no_public_backup_folder_selected">Ниједна фасцикла није изабрана</string>
</resources>

@ -227,4 +227,6 @@
<string name="activity_not_found">Ingen app som stödjer åtgärden hittades.</string>
<string name="pref_midnight_delay_title">Förläng dagen några timmar efter midnatt</string>
<string name="pref_midnight_delay_description">Vänta till 3:00 innan en ny dag visas. Användbart om du vanligtvis går och lägger dig efter midnatt. Kräver omstart av appen.</string>
<string name="select_public_backup_folder">Välj offentlig säkerhetskopieringsmapp</string>
<string name="no_public_backup_folder_selected">Ingen mapp vald</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">இந்தச் செயலைச் செய்வதற்கான பயன்பாடு எதுவுமில்லை.</string>
<string name="pref_midnight_delay_title">நாளை, நள்ளிரவை தாண்டி சில மணிநேரங்கள் நீட்டிக்கவும்</string>
<string name="pref_midnight_delay_description">புதிய நாளைக் காண்பிக்க அதிகாலை 3:00 மணிவரை காத்திருக்கவும். நீங்கள் பொதுவாக நடுஇரவுக்கு பின் உறங்குபவரரெனில் உபயோகமானது. பயன்பாட்டை மறுதுவக்கம் செய்தல் அவசியமானது</string>
<string name="select_public_backup_folder">பொது காப்பு கோப்புறையைத் தேர்ந்தெடுக்கவும்</string>
<string name="no_public_backup_folder_selected">எந்த கோப்புறையும் தேர்ந்தெடுக்கப்படவில்லை</string>
</resources>

@ -52,4 +52,6 @@
<string name="interval_always_ask">ఎల్లప్పుడూ అడుగు</string>
<string name="settings">సెట్టింగులు</string>
<string name="clear_label">తొలగించుము</string>
<string name="select_public_backup_folder">పబ్లిక్ బ్యాకప్ ఫోల్డర్‌ను ఎంచుకోండి</string>
<string name="no_public_backup_folder_selected">ఏ ఫోల్డర్‌ కూడా ఎంపిక చేయబడలేదు</string>
</resources>

@ -218,4 +218,6 @@
<string name="activity_not_found">Bu işlemi gerçekleştirebilecek bir uygulama bulunamadı.</string>
<string name="pref_midnight_delay_title">Yeni günü gece yarısından birkaç saat sonra başlat</string>
<string name="pref_midnight_delay_description">Yeni gün saat 03:00\'ten sonra başlar. Geç saatlerde uyuyanlar için kullanışlıdır. Uygulamanın yeniden başlatılmasını gerektirir.</string>
<string name="select_public_backup_folder">Genel yedekleme klasörünü seç</string>
<string name="no_public_backup_folder_selected">Klasör seçilmedi</string>
</resources>

@ -28,4 +28,6 @@
<string name="color_picker_default_title">رەڭ ئۆزگەردى</string>
<string name="toast_habit_created">ئادەت قۇرۇلدى</string>
<string name="settings">تەڭشەك</string>
<string name="select_public_backup_folder">ئاممىۋى زاپاس قىسقۇچىنى تاللاڭ</string>
<string name="no_public_backup_folder_selected">ھېچقانداق قىسقۇچ تاللانمىدى</string>
</resources>

@ -239,4 +239,6 @@
<string name="activity_not_found">Не знайдено програми для підтримки цієї дії</string>
<string name="pref_midnight_delay_title">Продовжити день на кілька годин після опівночі</string>
<string name="pref_midnight_delay_description">Почекати до 3:00 перед показом нового дня. Корисно, якщо ви зазвичай лягаєте спати після опівночі. Потрібно перезапустити програму.</string>
<string name="select_public_backup_folder">Виберіть загальнодоступну теку для резервних копій</string>
<string name="no_public_backup_folder_selected">Теку не вибрано</string>
</resources>

@ -212,4 +212,6 @@
<string name="activity_not_found">Không tìm thấy ứng dụng nào để hỗ trợ hành động này</string>
<string name="pref_midnight_delay_title">Kéo dài ngày thêm một vài giờ sau nửa đêm</string>
<string name="pref_midnight_delay_description">Chờ đến 3:00 sáng để hiện một ngày mới. Rất hữu ích nếu bạn thường đi ngủ sau nửa đêm. Yêu cầu khởi động lại ứng dụng.</string>
<string name="select_public_backup_folder">Chọn thư mục sao lưu công khai</string>
<string name="no_public_backup_folder_selected">Không có thư mục nào được chọn</string>
</resources>

@ -213,4 +213,6 @@
<string name="activity_not_found">找不到支持此操作的应用</string>
<string name="pref_midnight_delay_title">将一天延长到午夜后的几个小时</string>
<string name="pref_midnight_delay_description">凌晨 3 点后再显示新的一天。如果你通常在午夜后入睡,这会很有用。重启应用后生效。</string>
<string name="select_public_backup_folder">选择公共备份文件夹</string>
<string name="no_public_backup_folder_selected">未选择文件夹</string>
</resources>

@ -216,4 +216,6 @@
<string name="activity_not_found">找不到可以處理這個動作的應用程式。</string>
<string name="pref_midnight_delay_title">將一天延長到午夜過後幾個小時</string>
<string name="pref_midnight_delay_description">凌晨3點後再顯示新的一天。如果你通常在午夜以後才睡這能幫上忙。重新啟動後才生效。</string>
<string name="select_public_backup_folder">選擇公開備份資料夾</string>
<string name="no_public_backup_folder_selected">未選取資料夾</string>
</resources>

@ -137,6 +137,8 @@
<string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
<string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back.</string>
<string name="export_full_backup_summary">Generates a file that contains all your data. This file can be imported back.</string>
<string name="select_public_backup_folder">Select public backup folder</string>
<string name="no_public_backup_folder_selected">No folder selected</string>
<string name="bug_report_failed">Failed to generate bug report.</string>
<string name="generate_bug_report">Generate bug report</string>
<string name="troubleshooting">Troubleshooting</string>

@ -136,6 +136,12 @@
android:title="@string/import_data"
app:iconSpaceReserved="false" />
<Preference
android:key="publicBackupFolder"
android:title="@string/select_public_backup_folder"
android:summary="@string/no_public_backup_folder_selected"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory

Loading…
Cancel
Save