mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 01:08:50 -06:00
Compare commits
13 Commits
5aa8744ef4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b24759d6f | |||
| 46f6d297d7 | |||
| 543be48cb1 | |||
| 297a381e67 | |||
| 542750207e | |||
| 46a3b69971 | |||
|
|
9509b9e7af | ||
|
|
7a04abc8e5 | ||
|
|
b7a7d5f076 | ||
|
|
08a71b071c | ||
|
|
403d1058aa | ||
|
|
0a4086ec8c | ||
|
|
c987144f4a |
31
.github/workflows/main.yml
vendored
31
.github/workflows/main.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
jobs:
|
||||
Test:
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build project
|
||||
run: ./build.sh build
|
||||
|
||||
- name: Run Android tests
|
||||
run: ./build.sh android-tests-parallel 28 29 30 32 33 34
|
||||
|
||||
- name: Upload artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: |
|
||||
build/*log
|
||||
uhabits-android/build/outputs
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<h1 align="center">Loop Habit Tracker</h1>
|
||||
<p align="center">
|
||||
<a href="https://github.com/iSoron/uhabits/actions?query=workflow%3A%22Build+%26+Test%22">
|
||||
<img alt="Build & Test" src="https://github.com/iSoron/uhabits/workflows/Build%20&%20Test/badge.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/iSoron/uhabits/releases/latest">
|
||||
<img alt="release" src="https://img.shields.io/github/v/release/iSoron/uhabits" />
|
||||
</a>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -203,7 +203,8 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
||||
}
|
||||
|
||||
private fun getPopupAnchor(): View? {
|
||||
val dialog = supportFragmentManager.findFragmentByTag("historyEditor") as HistoryEditorDialog?
|
||||
val dialog =
|
||||
supportFragmentManager.findFragmentByTag("historyEditor") as HistoryEditorDialog?
|
||||
return dialog?.dataView
|
||||
}
|
||||
|
||||
@@ -216,6 +217,25 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
||||
ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> {
|
||||
showMessage(resources.getString(R.string.could_not_export))
|
||||
}
|
||||
|
||||
ShowHabitMenuPresenter.Message.HABIT_ARCHIVED -> {
|
||||
showMessage(
|
||||
resources.getQuantityString(
|
||||
R.plurals.toast_habits_archived,
|
||||
1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ShowHabitMenuPresenter.Message.HABIT_UNARCHIVED -> {
|
||||
showMessage(
|
||||
resources.getQuantityString(
|
||||
R.plurals.toast_habits_unarchived,
|
||||
1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ class ShowHabitMenu(
|
||||
if (preferences.isDeveloper) {
|
||||
menu.findItem(R.id.action_randomize).isVisible = true
|
||||
}
|
||||
menu.findItem(R.id.action_archive_habit).isVisible = presenter.canArchive()
|
||||
menu.findItem(R.id.action_unarchive_habit).isVisible = presenter.canUnarchive()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -44,6 +47,15 @@ class ShowHabitMenu(
|
||||
presenter.onEditHabit()
|
||||
return true
|
||||
}
|
||||
R.id.action_archive_habit -> {
|
||||
presenter.onArchiveHabits()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_unarchive_habit -> {
|
||||
presenter.onUnarchiveHabits()
|
||||
return true
|
||||
}
|
||||
R.id.action_delete -> {
|
||||
presenter.onDeleteHabit()
|
||||
return true
|
||||
|
||||
@@ -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,11 +62,22 @@ class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeLis
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == RINGTONE_REQUEST_CODE) {
|
||||
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()
|
||||
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 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()
|
||||
if (now - newestTimestamp > DateUtils.DAY_LENGTH) {
|
||||
DatabaseUtils.saveDatabaseCopy(context, dir)
|
||||
} else {
|
||||
Log.i("AutoBackup", "Fresh backup found (timestamp=$newestTimestamp)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun listBackupFiles(): ArrayList<File> {
|
||||
val files = ArrayList<File>()
|
||||
for (path in basedir.list()!!) {
|
||||
files.add(File("${basedir.path}/$path"))
|
||||
}
|
||||
return files
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 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
|
||||
|
||||
@@ -94,8 +94,9 @@
|
||||
<EditText
|
||||
android:id="@+id/nameInput"
|
||||
style="@style/FormInput"
|
||||
android:maxLines="1"
|
||||
android:inputType="textCapSentences"
|
||||
android:maxLines="2"
|
||||
android:maxLength="50"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:hint="@string/yes_or_no_short_example"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -26,6 +26,16 @@
|
||||
android:title="@string/export"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_archive_habit"
|
||||
android:title="@string/archive"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_unarchive_habit"
|
||||
android:title="@string/unarchive"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:title="@string/delete"
|
||||
|
||||
@@ -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…</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
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
*/
|
||||
package org.isoron.uhabits.core.ui.screens.habits.show
|
||||
|
||||
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
|
||||
import org.isoron.uhabits.core.commands.CommandRunner
|
||||
import org.isoron.uhabits.core.commands.DeleteHabitsCommand
|
||||
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
|
||||
import org.isoron.uhabits.core.models.Entry
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.models.HabitList
|
||||
@@ -40,10 +42,24 @@ class ShowHabitMenuPresenter(
|
||||
private val system: System,
|
||||
private val taskRunner: TaskRunner
|
||||
) {
|
||||
|
||||
fun canArchive(): Boolean {
|
||||
return !(habit.isArchived)
|
||||
}
|
||||
|
||||
fun canUnarchive(): Boolean {
|
||||
return habit.isArchived
|
||||
}
|
||||
|
||||
fun onEditHabit() {
|
||||
screen.showEditHabitScreen(habit)
|
||||
}
|
||||
|
||||
fun onArchiveHabits() {
|
||||
commandRunner.run(ArchiveHabitsCommand(habitList, listOf(habit)))
|
||||
screen.showMessage(Message.HABIT_ARCHIVED)
|
||||
}
|
||||
|
||||
fun onExportCSV() {
|
||||
val outputDir = system.getCSVOutputDir()
|
||||
taskRunner.execute(
|
||||
@@ -64,6 +80,11 @@ class ShowHabitMenuPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
fun onUnarchiveHabits() {
|
||||
commandRunner.run(UnarchiveHabitsCommand(habitList, listOf(habit)))
|
||||
screen.showMessage(Message.HABIT_UNARCHIVED)
|
||||
}
|
||||
|
||||
fun onRandomize() {
|
||||
val random = Random()
|
||||
habit.originalEntries.clear()
|
||||
@@ -72,7 +93,10 @@ class ShowHabitMenuPresenter(
|
||||
if (i % 7 == 0) strength = max(0.0, min(100.0, strength + 10 * random.nextGaussian()))
|
||||
if (random.nextInt(100) > strength) continue
|
||||
var value = Entry.YES_MANUAL
|
||||
if (habit.isNumerical) value = (1000 + 250 * random.nextGaussian() * strength / 100).toInt() * 1000
|
||||
if (habit.isNumerical) {
|
||||
value =
|
||||
(1000 + 250 * random.nextGaussian() * strength / 100).toInt() * 1000
|
||||
}
|
||||
habit.originalEntries.add(Entry(DateUtils.getToday().minus(i), value))
|
||||
}
|
||||
habit.recompute()
|
||||
@@ -80,7 +104,9 @@ class ShowHabitMenuPresenter(
|
||||
}
|
||||
|
||||
enum class Message {
|
||||
COULD_NOT_EXPORT
|
||||
COULD_NOT_EXPORT,
|
||||
HABIT_ARCHIVED,
|
||||
HABIT_UNARCHIVED
|
||||
}
|
||||
|
||||
interface Screen {
|
||||
|
||||
Reference in New Issue
Block a user