Compare commits

...

7 Commits

@ -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()

@ -26,7 +26,6 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.databinding.SettingsActivityBinding
import org.isoron.uhabits.utils.applyBottomInset
import org.isoron.uhabits.utils.applyRootViewInsets
import org.isoron.uhabits.utils.setupToolbar
@ -45,7 +44,6 @@ class SettingsActivity : AppCompatActivity() {
theme = themeSwitcher.currentTheme
)
binding.root.applyRootViewInsets()
binding.root.applyBottomInset()
setContentView(binding.root)
}
}

@ -24,13 +24,18 @@ 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
import android.view.View
import android.view.ViewGroup
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.RESULT_BUG_REPORT
@ -44,6 +49,7 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames
import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel
import org.isoron.uhabits.notifications.RingtoneManager
import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.applyBottomInset
import org.isoron.uhabits.utils.startActivitySafely
import org.isoron.uhabits.widgets.WidgetUpdater
import java.util.Calendar
@ -56,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)
}
@ -94,6 +111,15 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
super.onViewCreated(view, savedInstanceState)
}
override fun onCreateRecyclerView(
inflater: LayoutInflater?,
parent: ViewGroup?,
savedInstanceState: Bundle?,
): RecyclerView? {
return super.onCreateRecyclerView(inflater, parent, savedInstanceState)
.also { it.applyBottomInset() }
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val key = preference.key ?: return false
when (key) {
@ -114,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)
}
@ -128,6 +164,7 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
devCategory.isVisible = false
}
updateWeekdayPreference()
updatePublicBackupFolderSummary()
findPreference("reminderSound").isVisible = false
}
@ -192,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>

@ -160,7 +160,7 @@
</style>
<style name="PreferenceThemeOverlay.v14.Material.PureBlack">
<item name="android:background">@color/black</item>
<item name="colorControlHighlight">@color/white_a0</item>
</style>

@ -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