Local lists

pull/1004/head
Alex Baker 6 years ago
parent ae6cfca8ec
commit 346bf22fb3

@ -42,8 +42,8 @@ android {
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = 90501
versionName = "9.5"
versionCode = 90600
versionName = "9.6"
targetSdkVersion(Versions.targetSdk)
minSdkVersion(Versions.minSdk)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

@ -238,63 +238,6 @@ class TaskMoverTest : InjectingTestCase() {
assertEquals(2, taskDao.fetch(3)!!.parent)
}
@Test
fun dontSyncGoogleTask() {
createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
dontSync(1)
assertNull(googleTaskDao.getByTaskId(1))
assertFalse(taskDao.fetch(1)!!.isDeleted)
}
@Test
fun dontSyncCaldavTask() {
createTasks(1)
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
dontSync(1)
assertNull(caldavDao.getTask(1))
assertFalse(taskDao.fetch(1)!!.isDeleted)
}
@Test
fun dontSyncGoogleTaskWithSubtasks() {
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
dontSync(1)
assertNull(googleTaskDao.getByTaskId(2))
val task = taskDao.fetch(2)!!
assertFalse(task.isDeleted)
assertEquals(1, task.parent)
assertEquals(taskDao.fetch(1)!!.uuid, task.parentUuid)
}
@Test
fun dontSyncCaldavWithSubtasks() {
createTasks(1)
createSubtask(2, 1)
createSubtask(3, 2)
caldavDao.insert(
listOf(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))))
dontSync(1)
assertNull(caldavDao.getTask(3))
val task = taskDao.fetch(3)!!
assertFalse(task.isDeleted)
assertEquals(2, task.parent)
assertEquals(taskDao.fetch(2)!!.uuid, task.parentUuid)
}
@Test
fun moveToSameGoogleTaskListIsNoop() {
createTasks(1)
@ -356,9 +299,5 @@ class TaskMoverTest : InjectingTestCase() {
taskMover.move(tasks.toList(), CaldavFilter(CaldavCalendar("", calendar)))
}
private fun dontSync(task: Long) {
taskMover.move(listOf(task), null)
}
override fun inject(component: TestComponent) = component.inject(this)
}

@ -1,14 +1,18 @@
package org.tasks.data
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.natpryce.makeiteasy.MakeItEasy
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.dao.TaskDao
import org.junit.Assert.assertTrue
import com.todoroo.astrid.helper.UUIDHelper
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.data.CaldavDao.Companion.LOCAL
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.TestComponent
import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils
@ -18,6 +22,7 @@ import javax.inject.Inject
class DeletionDaoTests : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var deletionDao: DeletionDao
@Inject lateinit var caldavDao: CaldavDao
@Test
fun deleting1000DoesntCrash() {
@ -31,7 +36,7 @@ class DeletionDaoTests : InjectingTestCase() {
@Test
fun markDeletedUpdatesModificationTime() {
var task = newTask(MakeItEasy.with(CREATION_TIME, DateTime().minusMinutes(1)))
var task = newTask(with(CREATION_TIME, DateTime().minusMinutes(1)))
taskDao.createNew(task)
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
@ -41,7 +46,7 @@ class DeletionDaoTests : InjectingTestCase() {
@Test
fun markDeletedUpdatesDeletionTime() {
var task = newTask(MakeItEasy.with(CREATION_TIME, DateTime().minusMinutes(1)))
var task = newTask(with(CREATION_TIME, DateTime().minusMinutes(1)))
taskDao.createNew(task)
deletionDao.markDeleted(listOf(task.id))
task = taskDao.fetch(task.id)!!
@ -49,5 +54,41 @@ class DeletionDaoTests : InjectingTestCase() {
assertTrue(task.deletionDate < DateTimeUtils.currentTimeMillis())
}
@Test
fun purgeDeletedLocalTask() {
val task = newTask(with(DELETION_TIME, newDateTime()))
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar("", "1234").apply { account = LOCAL })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
assertNull(taskDao.fetch(task.id))
}
@Test
fun dontPurgeActiveTasks() {
val task = newTask()
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar("", "1234").apply { account = LOCAL })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
assertNotNull(taskDao.fetch(task.id))
}
@Test
fun dontPurgeDeletedCaldavTask() {
val task = newTask(with(DELETION_TIME, newDateTime()))
taskDao.createNew(task)
caldavDao.insert(CaldavCalendar("", "1234").apply { account = UUIDHelper.newUUID() })
caldavDao.insert(CaldavTask(task.id, "1234"))
deletionDao.purgeDeleted()
assertNotNull(taskDao.fetch(task.id))
}
override fun inject(component: TestComponent) = component.inject(this)
}

@ -300,6 +300,10 @@
android:name=".caldav.CaldavCalendarSettingsActivity"
android:theme="@style/Tasks"/>
<activity
android:name=".caldav.LocalListSettingsActivity"
android:theme="@style/Tasks"/>
<activity
android:name=".activities.PlaceSettingsActivity"
android:theme="@style/Tasks" />

@ -55,7 +55,6 @@ import org.tasks.R
import org.tasks.ShortcutManager
import org.tasks.activities.*
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.caldav.CaldavCalendarSettingsActivity
import org.tasks.data.CaldavDao
import org.tasks.data.TagDataDao
import org.tasks.data.TaskContainer
@ -63,7 +62,6 @@ import org.tasks.db.DbUtils
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.SortDialog
import org.tasks.etesync.EteSyncCalendarSettingsActivity
import org.tasks.filters.PlaceFilter
import org.tasks.injection.FragmentComponent
import org.tasks.injection.InjectingFragment
@ -360,9 +358,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
R.id.menu_caldav_list_fragment -> {
val calendar = (filter as CaldavFilter).calendar
val account = caldavDao.getAccountByUuid(calendar.account!!)
val caldavSettings = Intent(
activity,
if (account!!.isCaldavAccount) CaldavCalendarSettingsActivity::class.java else EteSyncCalendarSettingsActivity::class.java)
val caldavSettings = Intent(activity, account!!.listSettingsClass())
caldavSettings.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR, calendar)
startActivityForResult(caldavSettings, REQUEST_LIST_SETTINGS)
true

@ -153,7 +153,6 @@ open class TaskAdapter(
}
newParent.isGoogleTask -> changeGoogleTaskParent(task, newParent)
newParent.isCaldavTask -> changeCaldavParent(task, newParent)
else -> changeLocalParent(task, newParent)
}
}
@ -228,17 +227,9 @@ open class TaskAdapter(
when {
task.isGoogleTask -> changeGoogleTaskParent(task, null)
task.isCaldavTask -> changeCaldavParent(task, null)
else -> changeLocalParent(task, null)
}
}
private fun changeLocalParent(task: TaskContainer, newParent: TaskContainer?) {
val t = task.getTask()
t.parent = newParent?.id ?: 0
t.parentUuid = newParent?.uuid
taskDao.save(t, null)
}
private fun changeGoogleTaskParent(task: TaskContainer, newParent: TaskContainer?) {
val list = newParent?.googleTaskList ?: task.googleTaskList!!
if (newParent == null || task.googleTaskList == newParent.googleTaskList) {
@ -292,7 +283,7 @@ open class TaskAdapter(
} else {
caldavDao.update(caldavTask)
}
taskDao.setParent(newParentId, null, listOf(task.id))
taskDao.setParent(newParentId, listOf(task.id))
taskDao.touch(task.id)
localBroadcastManager.broadcastRefresh()
}

@ -17,6 +17,7 @@ import android.text.TextUtils;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskMover;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -55,6 +56,7 @@ public class TasksXmlImporter {
private final AlarmDao alarmDao;
private final TagDao tagDao;
private final GoogleTaskDao googleTaskDao;
private final TaskMover taskMover;
private final LocationDao locationDao;
private Activity activity;
private Handler handler;
@ -75,7 +77,8 @@ public class TasksXmlImporter {
LocalBroadcastManager localBroadcastManager,
AlarmDao alarmDao,
TagDao tagDao,
GoogleTaskDao googleTaskDao) {
GoogleTaskDao googleTaskDao,
TaskMover taskMover) {
this.tagDataDao = tagDataDao;
this.userActivityDao = userActivityDao;
this.dialogBuilder = dialogBuilder;
@ -85,6 +88,7 @@ public class TasksXmlImporter {
this.alarmDao = alarmDao;
this.tagDao = tagDao;
this.googleTaskDao = googleTaskDao;
this.taskMover = taskMover;
}
private void setProgressMessage(final String message) {
@ -102,6 +106,7 @@ public class TasksXmlImporter {
() -> {
try {
performImport();
taskMover.migrateLocalTasks();
} catch (IOException | XmlPullParserException e) {
Timber.e(e);
}

@ -162,19 +162,8 @@ abstract class TaskDao(private val database: Database) {
@Query("UPDATE tasks SET modified = strftime('%s','now')*1000 WHERE _id in (:ids)")
abstract fun touchInternal(ids: List<Long>)
@Query("UPDATE tasks SET parent = IFNULL(("
+ " SELECT parent._id FROM tasks AS parent"
+ " WHERE parent.remoteId = tasks.parent_uuid AND parent.deleted = 0), 0)"
+ "WHERE parent_uuid IS NOT NULL AND parent_uuid != ''")
abstract fun updateParents()
@Query("UPDATE tasks SET parent_uuid = "
+ " (SELECT parent.remoteId FROM tasks AS parent WHERE parent._id = tasks.parent)"
+ " WHERE parent > 0 AND _id IN (:tasks)")
abstract fun updateParentUids(tasks: List<Long>)
@Query("UPDATE tasks SET parent = :parent, parent_uuid = :parentUuid WHERE _id IN (:children)")
abstract fun setParent(parent: Long, parentUuid: String?, children: List<Long>)
@Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children)")
abstract fun setParent(parent: Long, children: List<Long>)
@Transaction
open fun fetchChildren(id: Long): List<Task> {
@ -281,6 +270,9 @@ abstract class TaskDao(private val database: Database) {
return tasks.map(TaskContainer::getTask)
}
@Query("SELECT _id FROM tasks LEFT JOIN google_tasks ON _id = gt_task AND gt_deleted = 0 LEFT JOIN caldav_tasks ON _id = cd_task AND cd_deleted = 0 WHERE gt_id IS NULL AND cd_id IS NULL AND parent = 0")
abstract fun getLocalTasks(): List<Long>
/** Generates SQL clauses */
object TaskCriteria {
/** @return tasks that have not yet been completed or deleted

@ -7,7 +7,6 @@ import androidx.annotation.IntDef
import androidx.core.os.ParcelCompat
import androidx.room.*
import com.google.ical.values.RRule
import com.todoroo.andlib.data.Property
import com.todoroo.andlib.data.Table
import com.todoroo.andlib.sql.Field
import com.todoroo.andlib.utility.DateUtilities
@ -111,6 +110,7 @@ class Task : Parcelable {
@Transient
var parent = 0L
@Deprecated(message = "no longer used")
@ColumnInfo(name = "parent_uuid")
var parentUuid: String? = null
@ -170,7 +170,6 @@ class Task : Parcelable {
transitoryData = parcel.readHashMap(ContentValues::class.java.classLoader) as HashMap<String, Any>?
isCollapsed = ParcelCompat.readBoolean(parcel)
parent = parcel.readLong()
parentUuid = parcel.readString()
}
var uuid: String
@ -307,7 +306,6 @@ class Task : Parcelable {
dest.writeMap(transitoryData as Map<*, *>?)
ParcelCompat.writeBoolean(dest, isCollapsed)
dest.writeLong(parent)
dest.writeString(parentUuid)
}
fun insignificantChange(task: Task?): Boolean {
@ -333,7 +331,7 @@ class Task : Parcelable {
&& recurrence == task.recurrence
&& repeatUntil == task.repeatUntil
&& calendarURI == task.calendarURI
&& parent == task.parent && parentUuid == task.parentUuid
&& parent == task.parent
&& remoteId == task.remoteId
}
@ -444,7 +442,6 @@ class Task : Parcelable {
if (remoteId != other.remoteId) return false
if (isCollapsed != other.isCollapsed) return false
if (parent != other.parent) return false
if (parentUuid != other.parentUuid) return false
if (transitoryData != other.transitoryData) return false
return true
@ -474,13 +471,12 @@ class Task : Parcelable {
result = 31 * result + remoteId.hashCode()
result = 31 * result + isCollapsed.hashCode()
result = 31 * result + parent.hashCode()
result = 31 * result + (parentUuid?.hashCode() ?: 0)
result = 31 * result + (transitoryData?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "Task(id=$id, title=$title, priority=$priority, dueDate=$dueDate, hideUntil=$hideUntil, creationDate=$creationDate, modificationDate=$modificationDate, completionDate=$completionDate, deletionDate=$deletionDate, notes=$notes, estimatedSeconds=$estimatedSeconds, elapsedSeconds=$elapsedSeconds, timerStart=$timerStart, reminderFlags=$reminderFlags, reminderPeriod=$reminderPeriod, reminderLast=$reminderLast, reminderSnooze=$reminderSnooze, recurrence=$recurrence, repeatUntil=$repeatUntil, calendarURI=$calendarURI, remoteId='$remoteId', isCollapsed=$isCollapsed, parent=$parent, parentUuid=$parentUuid, transitoryData=$transitoryData)"
return "Task(id=$id, title=$title, priority=$priority, dueDate=$dueDate, hideUntil=$hideUntil, creationDate=$creationDate, modificationDate=$modificationDate, completionDate=$completionDate, deletionDate=$deletionDate, notes=$notes, estimatedSeconds=$estimatedSeconds, elapsedSeconds=$elapsedSeconds, timerStart=$timerStart, reminderFlags=$reminderFlags, reminderPeriod=$reminderPeriod, reminderLast=$reminderLast, reminderSnooze=$reminderSnooze, recurrence=$recurrence, repeatUntil=$repeatUntil, calendarURI=$calendarURI, remoteId='$remoteId', isCollapsed=$isCollapsed, parent=$parent, transitoryData=$transitoryData)"
}
@Retention(AnnotationRetention.SOURCE)

@ -6,7 +6,8 @@ import static com.google.common.collect.Lists.transform;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static java.util.Collections.emptyList;
import androidx.annotation.Nullable;
import android.content.Context;
import androidx.annotation.NonNull;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
@ -17,15 +18,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
public class TaskMover {
private final Context context;
private final TaskDao taskDao;
private final CaldavDao caldavDao;
private final GoogleTaskDao googleTaskDao;
@ -35,12 +41,14 @@ public class TaskMover {
@Inject
public TaskMover(
@ForApplication Context context,
TaskDao taskDao,
CaldavDao caldavDao,
GoogleTaskDao googleTaskDao,
GoogleTaskListDao googleTaskListDao,
Preferences preferences,
LocalBroadcastManager localBroadcastManager) {
this.context = context;
this.taskDao = taskDao;
this.caldavDao = caldavDao;
this.googleTaskDao = googleTaskDao;
@ -64,11 +72,11 @@ public class TaskMover {
return null;
}
public void move(List<Long> tasks, Filter selectedList) {
public void move(List<Long> tasks, @NonNull Filter selectedList) {
tasks = new ArrayList<>(tasks);
tasks.removeAll(googleTaskDao.findChildrenInList(tasks));
tasks.removeAll(taskDao.findChildrenInList(tasks));
taskDao.setParent(0, null, tasks);
taskDao.setParent(0, tasks);
for (Task task : taskDao.fetch(tasks)) {
performMove(task, selectedList);
}
@ -79,7 +87,12 @@ public class TaskMover {
localBroadcastManager.broadcastRefresh();
}
private void performMove(Task task, @Nullable Filter selectedList) {
public void migrateLocalTasks() {
CaldavCalendar list = caldavDao.setupLocalAccount(context);
move(taskDao.getLocalTasks(), new CaldavFilter(list));
}
private void performMove(Task task, @NonNull Filter selectedList) {
long id = task.getId();
GoogleTask googleTask = googleTaskDao.getByTaskId(id);
@ -97,7 +110,7 @@ public class TaskMover {
moveLocalTask(task, selectedList);
}
private void moveGoogleTask(Task task, GoogleTask googleTask, Filter selected) {
private void moveGoogleTask(Task task, GoogleTask googleTask, @NonNull Filter selected) {
if (selected instanceof GtasksFilter
&& googleTask.getListId().equals(((GtasksFilter) selected).getRemoteId())) {
return;
@ -134,12 +147,12 @@ public class TaskMover {
newChild.setRemoteParent(newParent.getRemoteId());
return newChild;
}));
} else {
taskDao.setParent(task.getId(), task.getUuid(), childIds);
} else if (BuildConfig.DEBUG) {
throw new IllegalArgumentException();
}
}
private void moveCaldavTask(Task task, CaldavTask caldavTask, Filter selected) {
private void moveCaldavTask(Task task, CaldavTask caldavTask, @NonNull Filter selected) {
if (selected instanceof CaldavFilter
&& caldavTask.getCalendar().equals(((CaldavFilter) selected).getUuid())) {
return;
@ -174,12 +187,12 @@ public class TaskMover {
}));
} else if (selected instanceof GtasksFilter) {
moveToGoogleTasks(id, childIds, (GtasksFilter) selected);
} else {
taskDao.updateParentUids(from(children).transform(CaldavTask::getTask).toList());
} else if (BuildConfig.DEBUG) {
throw new IllegalArgumentException();
}
}
private void moveLocalTask(Task task, @Nullable Filter selected) {
private void moveLocalTask(Task task, @NonNull Filter selected) {
if (selected instanceof GtasksFilter) {
moveToGoogleTasks(task.getId(), taskDao.getChildren(task.getId()), (GtasksFilter) selected);
} else if (selected instanceof CaldavFilter) {
@ -195,11 +208,13 @@ public class TaskMover {
}
caldavDao.insert(task, root, preferences.addTasksToTop());
caldavDao.insert(tasks.values());
} else if (BuildConfig.DEBUG) {
throw new IllegalArgumentException();
}
}
private void moveToGoogleTasks(long id, List<Long> children, GtasksFilter filter) {
taskDao.setParent(0, null, children);
taskDao.setParent(0, children);
String listId = filter.getRemoteId();
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addTasksToTop());
List<GoogleTask> newChildren = new ArrayList<>();

@ -38,10 +38,13 @@ class Upgrader @Inject constructor(
private val taskDao: TaskDao,
private val locationDao: LocationDao,
private val iCal: iCalendar,
private val widgetManager: AppWidgetManager) {
private val widgetManager: AppWidgetManager,
private val taskMover: TaskMover) {
fun upgrade(from: Int, to: Int) {
if (from > 0) {
if (from == 0) {
caldavDao.setupLocalAccount(context)
} else {
run(from, V4_9_5) { removeDuplicateTags() }
run(from, V5_3_0) { migrateFilters() }
run(from, V6_0_beta_1) { migrateDefaultSyncList() }
@ -56,6 +59,7 @@ class Upgrader @Inject constructor(
run(from, V8_8) { preferences.setBoolean(R.string.p_linkify_task_edit, true) }
run(from, V8_10) { migrateWidgets() }
run(from, V9_3) { applyCaldavOrder() }
run(from, V9_6) { taskMover.migrateLocalTasks() }
preferences.setBoolean(R.string.p_just_updated, true)
}
preferences.setCurrentVersion(to)
@ -297,6 +301,7 @@ class Upgrader @Inject constructor(
private const val V8_8 = 717
private const val V8_10 = 735
private const val V9_3 = 90300
const val V9_6 = 90600
@JvmStatic
fun getAndroidColor(context: Context, index: Int): Int {

@ -51,7 +51,7 @@ public abstract class BaseListSettingsActivity extends ThemedInjectingAppCompatA
TextView icon;
@BindView(R.id.toolbar)
Toolbar toolbar;
protected Toolbar toolbar;
@Inject DialogBuilder dialogBuilder;
@Inject ColorProvider colorProvider;

@ -65,7 +65,7 @@ class ListPicker : InjectingDialogFragment() {
override fun inject(component: DialogFragmentComponent) = component.inject(this)
private fun selectedList(list: Filter?) {
private fun selectedList(list: Filter) {
targetFragment!!.onActivityResult(
targetRequestCode,
Activity.RESULT_OK,
@ -75,7 +75,7 @@ class ListPicker : InjectingDialogFragment() {
private fun refresh() {
val noSelection = requireArguments().getBoolean(EXTRA_NO_SELECTION, false)
val selected: Filter? = if (noSelection) null else arguments?.getParcelable(EXTRA_SELECTED_FILTER)
disposables!!.add(Single.fromCallable(filterProvider::remoteListPickerItems)
disposables!!.add(Single.fromCallable(filterProvider::listPickerItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { items: List<FilterListItem>? -> filterAdapter.setData(items!!, selected) })
@ -106,22 +106,15 @@ class ListPicker : InjectingDialogFragment() {
private fun createDialog(
filterAdapter: FilterAdapter,
dialogBuilder: DialogBuilder,
handler: (Filter?) -> Unit): AlertDialog {
handler: (Filter) -> Unit): AlertDialog {
val builder = dialogBuilder
.newDialog()
.setNegativeButton(android.R.string.cancel, null)
.setSingleChoiceItems(
filterAdapter,
-1
) { dialog: DialogInterface, which: Int ->
if (which == 0) {
handler.invoke(null)
} else {
.setSingleChoiceItems(filterAdapter,-1) { dialog: DialogInterface, which: Int ->
val item = filterAdapter.getItem(which)
if (item is GtasksFilter || item is CaldavFilter) {
handler.invoke(item as Filter)
}
}
dialog.dismiss()
}
return builder.show()

@ -14,6 +14,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskMover;
import com.todoroo.astrid.service.Upgrader;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -64,6 +65,7 @@ public class TasksJsonImporter {
private final TaskAttachmentDao taskAttachmentDao;
private final CaldavDao caldavDao;
private final Preferences preferences;
private final TaskMover taskMover;
private final LocationDao locationDao;
private final ImportResult result = new ImportResult();
@ -82,7 +84,8 @@ public class TasksJsonImporter {
FilterDao filterDao,
TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao,
Preferences preferences) {
Preferences preferences,
TaskMover taskMover) {
this.tagDataDao = tagDataDao;
this.userActivityDao = userActivityDao;
this.taskDao = taskDao;
@ -96,6 +99,7 @@ public class TasksJsonImporter {
this.taskAttachmentDao = taskAttachmentDao;
this.caldavDao = caldavDao;
this.preferences = preferences;
this.taskMover = taskMover;
}
private void setProgressMessage(
@ -235,7 +239,6 @@ public class TasksJsonImporter {
googleTaskDao.updateParents();
caldavDao.updateParents();
taskDao.updateParents();
for (Entry<String, Integer> entry : backupContainer.getIntPrefs().entrySet()) {
if (P_CURRENT_VERSION.equals(entry.getKey())) {
@ -259,6 +262,9 @@ public class TasksJsonImporter {
R.string.p_theme_color,
Upgrader.getAndroidColor(context, themeIndex));
}
if (version < Upgrader.V9_6) {
taskMover.migrateLocalTasks();
}
reader.close();
is.close();

@ -34,7 +34,7 @@ public abstract class BaseCaldavCalendarSettingsActivity extends BaseListSetting
public static final String EXTRA_CALDAV_CALENDAR = "extra_caldav_calendar";
public static final String EXTRA_CALDAV_ACCOUNT = "extra_caldav_account";
@Inject CaldavDao caldavDao;
@Inject protected CaldavDao caldavDao;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.root_layout)

@ -0,0 +1,31 @@
package org.tasks.caldav
import android.os.Bundle
import org.tasks.R
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.injection.ActivityComponent
class LocalListSettingsActivity : BaseCaldavCalendarSettingsActivity() {
override fun getLayout() = R.layout.activity_caldav_calendar_settings
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbar.menu.findItem(R.id.delete)?.isVisible = caldavDao.getCalendarsByAccount(CaldavDao.LOCAL).size > 1
}
override fun inject(component: ActivityComponent) = component.inject(this)
override fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int) =
createSuccessful(null)
override fun updateNameAndColor(
account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) =
updateCalendar()
override fun deleteCalendar(caldavAccount: CaldavAccount, caldavCalendar: CaldavCalendar) =
onDeleted(true)
}

@ -8,6 +8,10 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.todoroo.astrid.data.Task
import org.tasks.activities.BaseListSettingsActivity
import org.tasks.caldav.CaldavCalendarSettingsActivity
import org.tasks.caldav.LocalListSettingsActivity
import org.tasks.etesync.EteSyncCalendarSettingsActivity
import org.tasks.security.KeyStoreEncryption
@Entity(tableName = "caldav_accounts")
@ -80,6 +84,12 @@ class CaldavAccount : Parcelable {
val isEteSyncAccount: Boolean
get() = accountType == TYPE_ETESYNC
fun listSettingsClass(): Class<out BaseListSettingsActivity> = when(accountType) {
TYPE_ETESYNC -> EteSyncCalendarSettingsActivity::class.java
TYPE_LOCAL -> LocalListSettingsActivity::class.java
else -> CaldavCalendarSettingsActivity::class.java
}
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
@ -137,6 +147,8 @@ class CaldavAccount : Parcelable {
companion object {
private const val TYPE_CALDAV = 0
const val TYPE_ETESYNC = 1
const val TYPE_LOCAL = 2
@JvmField val CREATOR: Parcelable.Creator<CaldavAccount> = object : Parcelable.Creator<CaldavAccount> {
override fun createFromParcel(source: Parcel): CaldavAccount? {
return CaldavAccount(source)

@ -1,11 +1,13 @@
package org.tasks.data
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.room.*
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.core.SortHelper.APPLE_EPOCH
import com.todoroo.astrid.data.Task
import io.reactivex.Single
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.R
import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.db.DbUtils
import org.tasks.filters.CaldavFilters
@ -19,11 +21,14 @@ abstract class CaldavDao {
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
abstract fun getCalendarByUuid(uuid: String): CaldavCalendar?
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :uuid")
abstract fun getCalendarsByAccount(uuid: String): List<CaldavCalendar>
@Query("SELECT * FROM caldav_accounts WHERE cda_uuid = :uuid LIMIT 1")
abstract fun getAccountByUuid(uuid: String): CaldavAccount?
@Query("SELECT COUNT(*) FROM caldav_accounts")
abstract fun accountCount(): Single<Int>
@Query("SELECT COUNT(*) FROM caldav_accounts WHERE cda_account_type != 2")
abstract fun accountCount(): Int
@Query("SELECT * FROM caldav_accounts ORDER BY cda_account_type, UPPER(cda_name)")
abstract fun getAccounts(): List<CaldavAccount>
@ -239,4 +244,30 @@ abstract class CaldavDao {
@Query("SELECT task.*, caldav_task.*, IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000) AS primary_sort FROM caldav_tasks AS caldav_task INNER JOIN tasks AS task ON _id = cd_task WHERE cd_calendar = :calendar AND parent = :parent AND cd_deleted = 0 AND deleted = 0 AND primary_sort >= :from AND primary_sort < IFNULL(:to, ${Long.MAX_VALUE}) ORDER BY primary_sort")
internal abstract fun getTasksToShift(calendar: String, parent: Long, from: Long, to: Long?): List<CaldavTaskContainer>
fun setupLocalAccount(context: Context): CaldavCalendar {
val account = getAccountByUuid(LOCAL) ?: createLocalAccount()
return getCalendarsByAccount(account.uuid!!).getOrElse(0) {
createLocalList(context, account)
}
}
private fun createLocalAccount(): CaldavAccount {
val account = CaldavAccount()
account.accountType = CaldavAccount.TYPE_LOCAL
account.uuid = LOCAL
account.id = insert(account)
return account
}
private fun createLocalList(context: Context, account: CaldavAccount): CaldavCalendar {
val list = CaldavCalendar(context.getString(R.string.default_list), UUIDHelper.newUUID())
list.account = account.uuid
insert(list)
return list
}
companion object {
const val LOCAL = "local"
}
}

@ -4,6 +4,7 @@ import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
import androidx.room.Transaction
import org.tasks.data.CaldavDao.Companion.LOCAL
import org.tasks.db.DbUtils
import java.util.*
@ -98,6 +99,9 @@ abstract class DeletionDao {
@Delete
abstract fun deleteCaldavAccount(caldavAccount: CaldavAccount)
@Query("DELETE FROM tasks WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task INNER JOIN caldav_lists ON cdl_uuid = cd_calendar WHERE cdl_account = '$LOCAL' AND deleted > 0)")
abstract fun purgeDeleted()
@Transaction
open fun delete(caldavAccount: CaldavAccount): List<Long> {
val deleted = ArrayList<Long>()

@ -189,7 +189,6 @@ public class TaskContainer {
} else {
task.setParent(parent);
}
task.setParentUuid(null);
}
public boolean hasParent() {

@ -109,9 +109,7 @@ public class FilterCriteriaProvider {
if (!googleTaskListDao.getAccounts().isEmpty()) {
result.add(getGtasksFilterCriteria());
}
if (!caldavDao.getAccounts().isEmpty()) {
result.add(getCaldavFilterCriteria());
}
return result;
}

@ -16,9 +16,8 @@ import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.TagSettingsActivity
import org.tasks.billing.Inventory
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.caldav.CaldavCalendarSettingsActivity
import org.tasks.data.*
import org.tasks.etesync.EteSyncCalendarSettingsActivity
import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType
import org.tasks.injection.ForApplication
import org.tasks.location.LocationPickerActivity
@ -41,13 +40,10 @@ class FilterProvider @Inject constructor(
private val preferences: Preferences,
private val locationDao: LocationDao) {
val remoteListPickerItems: List<FilterListItem>
val listPickerItems: List<FilterListItem>
get() {
AndroidUtilities.assertNotMainThread()
val items: MutableList<FilterListItem> = ArrayList()
val item = Filter(context.getString(R.string.dont_sync), null)
item.icon = R.drawable.ic_outline_cloud_off_24px
items.add(item)
for ((account, value) in googleTaskFilters) {
items.addAll(
getSubmenu(
@ -173,6 +169,9 @@ class FilterProvider @Inject constructor(
}
}
for ((account, value) in caldavFilters) {
if (account.accountType == TYPE_LOCAL && !preferences.getBoolean(R.string.p_lists_enabled, true)) {
continue
}
items.addAll(
getSubmenu(
account.name,
@ -187,9 +186,7 @@ class FilterProvider @Inject constructor(
NavigationDrawerAction(
context.getString(R.string.new_list),
R.drawable.ic_outline_add_24px,
Intent(
context,
if (account.isCaldavAccount) CaldavCalendarSettingsActivity::class.java else EteSyncCalendarSettingsActivity::class.java)
Intent(context, account.listSettingsClass())
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT, account),
NavigationDrawerFragment.REQUEST_NEW_LIST))
}
@ -261,6 +258,9 @@ class FilterProvider @Inject constructor(
val accounts = caldavDao.getAccounts()
val filters = LinkedHashMap<CaldavAccount, List<Filter>>()
for (account in accounts) {
if (account.accountType == TYPE_LOCAL) {
account.name = context.getString(R.string.lists)
}
filters[account] = if (account.isCollapsed) {
emptyList()
} else {

@ -12,6 +12,7 @@ import org.tasks.activities.attribution.AttributionActivity
import org.tasks.billing.PurchaseActivity
import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.caldav.CaldavCalendarSettingsActivity
import org.tasks.caldav.LocalListSettingsActivity
import org.tasks.dashclock.DashClockSettings
import org.tasks.drive.DriveLoginActivity
import org.tasks.etesync.EncryptionSettingsActivity
@ -75,4 +76,5 @@ interface ActivityComponent {
fun inject(activity: ManageSpaceActivity)
fun inject(activity: SyncPreferences)
fun inject(activity: PlaceSettingsActivity)
fun inject(activity: LocalListSettingsActivity)
}

@ -7,6 +7,7 @@ import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.timers.TimerPlugin;
import javax.inject.Inject;
import org.tasks.data.DeletionDao;
import org.tasks.data.Geofence;
import org.tasks.data.LocationDao;
import org.tasks.data.TaskAttachment;
@ -32,6 +33,7 @@ public class CleanupWork extends InjectingWorker {
@Inject TaskAttachmentDao taskAttachmentDao;
@Inject UserActivityDao userActivityDao;
@Inject LocationDao locationDao;
@Inject DeletionDao deletionDao;
public CleanupWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
@ -64,6 +66,7 @@ public class CleanupWork extends InjectingWorker {
}
}
timerPlugin.updateNotifications();
deletionDao.purgeDeleted();
return Result.success();
}

@ -31,6 +31,7 @@ import androidx.work.PeriodicWorkRequest;
import androidx.work.Worker;
import com.google.common.primitives.Longs;
import com.todoroo.astrid.data.Task;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.Random;
@ -155,7 +156,7 @@ public class WorkManager {
(forceAccountPresent == null
? zip(
googleTaskListDao.accountCount(),
caldavDao.accountCount(),
Single.fromCallable(caldavDao::accountCount),
(googleCount, caldavCount) -> googleCount > 0 || caldavCount > 0)
: just(forceAccountPresent))
.subscribeOn(Schedulers.io())

@ -41,8 +41,8 @@ class DefaultFilterProvider @Inject constructor(
get() = getFilterFromPreference(R.string.p_last_viewed_list)
set(filter) = setFilterPreference(filter, R.string.p_last_viewed_list)
var defaultRemoteList: Filter?
get() = getFilterFromPreference(R.string.p_default_remote_list)
var defaultRemoteList: Filter
get() = getFilterFromPreference(R.string.p_default_remote_list) ?: getAnyList()
set(filter) = setFilterPreference(filter, R.string.p_default_remote_list)
val startupFilter: Filter?
@ -60,6 +60,14 @@ class DefaultFilterProvider @Inject constructor(
fun getFilterFromPreference(prefString: String?): Filter? =
getFilterFromPreference(prefString, getMyTasksFilter(context.resources))
private fun getAnyList(): Filter {
val filter = googleTaskListDao.getAllLists().getOrNull(0)
?.let { GtasksFilter(it) }
?: CaldavFilter(caldavDao.getCalendars()[0])
defaultFilter = filter
return filter
}
private fun getFilterFromPreference(preferenceValue: String?, def: Filter?): Filter? {
if (!isNullOrEmpty(preferenceValue)) {
try {

@ -13,6 +13,7 @@ import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskListDao
@ -138,7 +139,9 @@ class Synchronization : InjectingPreferenceFragment() {
}
private fun addCaldavAccounts(category: PreferenceCategory): Boolean {
val accounts: List<CaldavAccount> = caldavDao.getAccounts()
val accounts: List<CaldavAccount> = caldavDao.getAccounts().filter {
it.accountType != TYPE_LOCAL
}
for (account in accounts) {
val preference = Preference(context)
preference.title = account.name

@ -18,9 +18,9 @@ import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import javax.inject.Inject
private const val FRAG_TAG_REMOTE_LIST_SELECTION = "frag_tag_remote_list_selection"
private const val FRAG_TAG_DEFAULT_LIST_SELECTION = "frag_tag_default_list_selection"
private const val FRAG_TAG_CALENDAR_PICKER = "frag_tag_calendar_picker"
private const val REQUEST_REMOTE_LIST = 10010
private const val REQUEST_DEFAULT_LIST = 10010
private const val REQUEST_CALENDAR_SELECTION = 10011
class TaskDefaults : InjectingPreferenceFragment() {
@ -49,9 +49,8 @@ class TaskDefaults : InjectingPreferenceFragment() {
ListPicker.newListPicker(
defaultFilterProvider.defaultRemoteList,
this,
REQUEST_REMOTE_LIST
)
.show(parentFragmentManager, FRAG_TAG_REMOTE_LIST_SELECTION)
REQUEST_DEFAULT_LIST)
.show(parentFragmentManager, FRAG_TAG_DEFAULT_LIST_SELECTION)
false
}
updateRemoteListSummary()
@ -60,11 +59,9 @@ class TaskDefaults : InjectingPreferenceFragment() {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_REMOTE_LIST) {
if (requestCode == REQUEST_DEFAULT_LIST) {
val list: Filter? = data!!.getParcelableExtra(ListPicker.EXTRA_SELECTED_FILTER)
if (list == null) {
preferences.setString(R.string.p_default_remote_list, null)
} else if (list is GtasksFilter || list is CaldavFilter) {
if (list is GtasksFilter || list is CaldavFilter) {
defaultFilterProvider.defaultRemoteList = list
} else {
throw RuntimeException("Unhandled filter type")
@ -91,9 +88,7 @@ class TaskDefaults : InjectingPreferenceFragment() {
private fun updateRemoteListSummary() {
val defaultFilter = defaultFilterProvider.defaultRemoteList
findPreference(R.string.p_default_remote_list).summary =
if (defaultFilter == null) getString(R.string.dont_sync)
else defaultFilter.listingTitle
findPreference(R.string.p_default_remote_list).summary = defaultFilter.listingTitle
}
override fun inject(component: FragmentComponent) = component.inject(this)

@ -46,6 +46,6 @@ public class SyncAdapters {
}
public boolean isCaldavSyncEnabled() {
return caldavDao.getAccounts().size() > 0;
return caldavDao.accountCount() > 0;
}
}

@ -6,7 +6,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import butterknife.BindView
import com.google.android.material.chip.ChipGroup
import com.todoroo.astrid.api.CaldavFilter
@ -26,9 +25,6 @@ import org.tasks.preferences.DefaultFilterProvider
import javax.inject.Inject
class ListFragment : TaskEditControlFragment() {
@BindView(R.id.dont_sync)
lateinit var textView: TextView
@BindView(R.id.chip_group)
lateinit var chipGroup: ChipGroup
@ -39,8 +35,8 @@ class ListFragment : TaskEditControlFragment() {
@Inject lateinit var taskMover: TaskMover
@Inject lateinit var chipProvider: ChipProvider
private var originalList: Filter? = null
private var selectedList: Filter? = null
private lateinit var originalList: Filter
private lateinit var selectedList: Filter
private lateinit var callback: OnListChanged
interface OnListChanged {
@ -56,8 +52,8 @@ class ListFragment : TaskEditControlFragment() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
if (savedInstanceState != null) {
originalList = savedInstanceState.getParcelable(EXTRA_ORIGINAL_LIST)
setSelected(savedInstanceState.getParcelable(EXTRA_SELECTED_LIST))
originalList = savedInstanceState.getParcelable(EXTRA_ORIGINAL_LIST)!!
setSelected(savedInstanceState.getParcelable(EXTRA_SELECTED_LIST)!!)
} else {
if (task.isNew) {
if (task.hasTransitory(GoogleTask.KEY)) {
@ -94,7 +90,7 @@ class ListFragment : TaskEditControlFragment() {
return view
}
private fun setSelected(filter: Filter?) {
private fun setSelected(filter: Filter) {
selectedList = filter
refreshView()
callback.onListChanged(filter)
@ -102,13 +98,9 @@ class ListFragment : TaskEditControlFragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (originalList != null) {
outState.putParcelable(EXTRA_ORIGINAL_LIST, originalList)
}
if (selectedList != null) {
outState.putParcelable(EXTRA_SELECTED_LIST, selectedList)
}
}
override val layout: Int
get() = R.layout.control_set_remote_list
@ -135,7 +127,6 @@ class ListFragment : TaskEditControlFragment() {
override fun apply(task: Task) {
if (isNew || hasChanges()) {
task.parent = 0
task.parentUuid = null
taskMover.move(listOf(task.id), selectedList)
}
}
@ -153,17 +144,17 @@ class ListFragment : TaskEditControlFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SELECT_LIST) {
if (resultCode == Activity.RESULT_OK) {
setList(data?.getParcelableExtra(ListPicker.EXTRA_SELECTED_FILTER))
data?.getParcelableExtra<Filter>(ListPicker.EXTRA_SELECTED_FILTER)?.let {
setList(it)
}
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun setList(list: Filter?) {
if (list == null) {
setSelected(null)
} else if (list is GtasksFilter || list is CaldavFilter) {
private fun setList(list: Filter) {
if (list is GtasksFilter || list is CaldavFilter) {
setSelected(list)
} else {
throw RuntimeException("Unhandled filter type")
@ -171,20 +162,11 @@ class ListFragment : TaskEditControlFragment() {
}
private fun refreshView() {
if (selectedList == null) {
textView.visibility = View.VISIBLE
chipGroup.visibility = View.GONE
} else {
textView.visibility = View.GONE
chipGroup.visibility = View.VISIBLE
chipGroup.removeAllViews()
val chip = chipProvider.newChip(selectedList, R.drawable.ic_list_24px, true, true)
chip.isCloseIconVisible = true
chip.setOnClickListener { openPicker() }
chip.setOnCloseIconClickListener { setSelected(null) }
chipGroup.addView(chip)
}
}
companion object {
const val TAG = R.string.TEA_ctrl_google_task_list

@ -136,7 +136,6 @@ class SubtaskControlSet : TaskEditControlFragment(), SubtaskViewHolder.Callbacks
}
else -> {
subtask.parent = task.id
subtask.parentUuid = task.uuid
taskDao.save(subtask)
}
}

@ -1,27 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.chip.ChipGroup xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/dont_sync"
style="@style/TaskEditTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:gravity="start"
android:hint="@string/dont_sync"
android:textAlignment="viewStart" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="@dimen/chip_spacing"
app:chipSpacingHorizontal="@dimen/chip_spacing"
android:visibility="gone" />
</LinearLayout>
app:chipSpacingHorizontal="@dimen/chip_spacing" />

@ -419,7 +419,6 @@
<string name="badges_description">Показвай броя задачи върху икона на Tasks в лаунчера. Не всички лаунчери поддържат значки.</string>
<string name="bundle_notifications_summary">Комбинирай множество нотификации в една</string>
<string name="repeat_monthly_same_day_each_month">на един и същ ден всеки месец</string>
<string name="dont_sync">Не синхронизирай</string>
<string name="repeat_monthly_every_day_of_nth_week">всеки %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">на всеки %1$s %2$s</string>
<string name="repeat_monthly_first_week">първи</string>

@ -434,7 +434,6 @@
<string name="badges_description">Zobrazit počet úkolů u ikony Tasks. Některé spouštěče tuto funkci nepodporují.</string>
<string name="bundle_notifications_summary">Seskupit několik upozornění do jednoho</string>
<string name="repeat_monthly_same_day_each_month">ve stejný den každý měsíc</string>
<string name="dont_sync">Nesynchronizovat</string>
<string name="repeat_monthly_every_day_of_nth_week">každý %1$s %2$s</string>
<string name="repeat_monthly_first_week">první</string>
<string name="repeat_monthly_second_week">druhý</string>

@ -410,7 +410,6 @@
<string name="badges_description">Anzeige eines Aufgabenzählers am Tasks Launcher Icon. Nicht alle Launcher unterstützen Badges.</string>
<string name="bundle_notifications_summary">Mehrere Benachrichtigungen in einer zusammenfassen</string>
<string name="repeat_monthly_same_day_each_month">am selben Tag jeden Monats</string>
<string name="dont_sync">Nicht synchronisieren</string>
<string name="repeat_monthly_every_day_of_nth_week">jeden %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">an jedem %1$s %2$s</string>
<string name="repeat_monthly_first_week">ersten</string>

@ -418,7 +418,6 @@
<string name="badges_description">Mostrar número de tareas en icono de Task. No todos los launchers lo soportan.</string>
<string name="bundle_notifications_summary">Combinar múltiples notificaciones en una sola</string>
<string name="repeat_monthly_same_day_each_month">el mismo día cada mes</string>
<string name="dont_sync">No sincronizar</string>
<string name="repeat_monthly_every_day_of_nth_week">cada %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">cada %1$s %2$s</string>
<string name="repeat_monthly_first_week">primer</string>

@ -254,7 +254,6 @@
<string name="calendar_settings">Kalendri seaded</string>
<string name="bundle_notifications">Koonda teavitused</string>
<string name="list">Nimekiri</string>
<string name="dont_sync">Ära sünkrooni</string>
<string name="repeat_monthly_first_week">esimene</string>
<string name="repeat_monthly_second_week">teine</string>
<string name="repeat_monthly_third_week">kolmas</string>

@ -424,7 +424,6 @@
<string name="badges_description">Bistaratu zeregin kopurua abiarazleko Tasks ikonoan. Abiarazle guztiek ez dituzte dominak onartzen.</string>
<string name="bundle_notifications_summary">Taldekatu hainbat jakinarazpen batean</string>
<string name="repeat_monthly_same_day_each_month">egun berean hilero</string>
<string name="dont_sync">Ez sinkronizatu</string>
<string name="repeat_monthly_every_day_of_nth_week">%1$s %2$s bakoitzean</string>
<string name="repeat_monthly_on_every_day_of_nth_week">%1$s %2$s bakoitzean</string>
<string name="repeat_monthly_first_week">lehen</string>

@ -391,7 +391,6 @@
<string name="badges_description">Näytä tehtävien lukumäärä Tasksin käynnistyskuvakkeessa. Kaikki käynnistimet eivät tue merkkejä.</string>
<string name="bundle_notifications_summary">Yhdistä useita ilmoituksia yhdeksi</string>
<string name="repeat_monthly_same_day_each_month">sama päivä joka kuukausi</string>
<string name="dont_sync">Älä synkronoi</string>
<string name="repeat_monthly_every_day_of_nth_week">joka %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">jokaisella %1$s %2$s</string>
<string name="repeat_monthly_first_week">ensimmäinen</string>

@ -400,7 +400,6 @@
<string name="badges_description">Afficher un compteur de tâche sur l\'icône du lanceur de Tasks. Certains lanceurs ne supportent pas les badges.</string>
<string name="bundle_notifications_summary">Combiner plusieurs notifications en une seule</string>
<string name="repeat_monthly_same_day_each_month">le même jour chaque mois</string>
<string name="dont_sync">Ne pas synchroniser</string>
<string name="repeat_monthly_every_day_of_nth_week">tous les %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">tous les %1$s %2$s</string>
<string name="repeat_monthly_first_week">première</string>

@ -419,7 +419,6 @@
<string name="badges_description">Feladatok számának megjelenítése a Tasks ikonon. Nem minden launcher által támogatott funkció.</string>
<string name="bundle_notifications_summary">Több értesítés összevonása eggyé</string>
<string name="repeat_monthly_same_day_each_month">minden hónap egyazon napján</string>
<string name="dont_sync">Szinkronizáció kikapcsolva</string>
<string name="repeat_monthly_every_day_of_nth_week">minden %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">minden %1$s %2$s</string>
<string name="repeat_monthly_first_week">első</string>

@ -352,7 +352,6 @@
<string name="badges_description">Tampilkan jumlah tugas pada ikon peluncur Tasks. Tidak semua peluncur mendukung fitur ini.</string>
<string name="bundle_notifications_summary">Gabung beberapa notifikasi menjadi satu notifikasi</string>
<string name="repeat_monthly_same_day_each_month">pada hari yang sama setiap bulan</string>
<string name="dont_sync">Jangan sinkron</string>
<string name="repeat_monthly_first_week">pertama</string>
<string name="repeat_monthly_second_week">kedua</string>
<string name="repeat_monthly_third_week">ketiga</string>

@ -415,7 +415,6 @@
<string name="badges_description">Mostra contatore di notifiche sull\'icona di Task. Non tutti i launcher lo supportano.</string>
<string name="bundle_notifications_summary">Combina diverse notifiche in una sola</string>
<string name="repeat_monthly_same_day_each_month">lo stesso giorno di ogni mese</string>
<string name="dont_sync">Non sincronizzare</string>
<string name="repeat_monthly_every_day_of_nth_week">ogni %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">ogni %1$s %2$s</string>
<string name="repeat_monthly_first_week">primo</string>

@ -453,7 +453,6 @@
<string name="badges_description">הצג מספר משימות על סמליל משגר Tasks. לא כל המשגרים תומכים בתגים.</string>
<string name="bundle_notifications_summary">שלב מספר התראות בהתראה אחת</string>
<string name="repeat_monthly_same_day_each_month">באותו היום בכל חודש</string>
<string name="dont_sync">ללא סנכרון</string>
<string name="repeat_monthly_every_day_of_nth_week">בכל %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">בכל %1$s %2$s</string>
<string name="repeat_monthly_first_week">ראשון</string>

@ -416,7 +416,6 @@
<string name="badges_description">Tasks ランチャーアイコンにタスク数を表示します。 すべてのランチャーがバッジをサポートしているわけではありません。</string>
<string name="bundle_notifications_summary">複数の通知を1つの通知にまとめる</string>
<string name="repeat_monthly_same_day_each_month">毎月同じ日に</string>
<string name="dont_sync">同期しない</string>
<string name="repeat_monthly_every_day_of_nth_week">%1$s %2$s ごと</string>
<string name="repeat_monthly_on_every_day_of_nth_week">%1$s %2$s ごとに</string>
<string name="repeat_monthly_first_week">第一週</string>

@ -418,7 +418,6 @@
<string name="badges_description">Tasks 앱 아이콘 위에 할일 개수를 표시합니다. 모든 런처가 배지 기능을 지원하지는 않습니다. </string>
<string name="bundle_notifications_summary">상태바에 알림이 여러 개 있을 경우 하나로 모아서 표시합니다</string>
<string name="repeat_monthly_same_day_each_month">매월 같은 날에</string>
<string name="dont_sync">동기화하지 않음</string>
<string name="repeat_monthly_every_day_of_nth_week">매 %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">매 %1$s %2$s 마다</string>
<string name="repeat_monthly_first_week">첫번째</string>

@ -415,7 +415,6 @@
<string name="badges_description">Rodyti užduočių kiekį Tasks paleidimo piktogramoje. Ne visos sistemos palaiko šią funkciją.</string>
<string name="bundle_notifications_summary">Sujungti kelis pranešimus į vieną pranešimą</string>
<string name="repeat_monthly_same_day_each_month">Tą pačią mėnesio dieną</string>
<string name="dont_sync">Nesinchronizuoti</string>
<string name="repeat_monthly_every_day_of_nth_week">Kiekvieną %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">Kiekvieną %1$s %2$s</string>
<string name="repeat_monthly_first_week">pirmą</string>

@ -424,7 +424,6 @@
<string name="default_calendar">Forvalgt kalender</string>
<string name="badges_description">Vis gjøremålsantall i Task-oppstarterikonet. Ikke alle oppstartere støtter merker.</string>
<string name="repeat_monthly_same_day_each_month">på samme dag hver måned</string>
<string name="dont_sync">Ikke synkroniser</string>
<string name="repeat_monthly_every_day_of_nth_week">hver %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">hver %1$s %2$s</string>
<string name="repeat_monthly_first_week">første</string>

@ -409,7 +409,6 @@
<string name="badges_description">Toon een taken teller bij de Tasks app icon. Niet alle launchers ondersteunen dit.</string>
<string name="bundle_notifications_summary">Combineer meerdere notificaties samen in één</string>
<string name="repeat_monthly_same_day_each_month">Op dezelfde dag elke maand</string>
<string name="dont_sync">Niet synchroniseren</string>
<string name="repeat_monthly_every_day_of_nth_week">elke %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">op elke %1$s %2$s</string>
<string name="repeat_monthly_first_week">eerste</string>

@ -430,7 +430,6 @@
<string name="badges_description">Wyświetl liczbę zadań na ikonie Tasks. Nie wszystkie launchery to wspierają.</string>
<string name="bundle_notifications_summary">Wyświetlaj wiele powiadomień jako jedno</string>
<string name="repeat_monthly_same_day_each_month">tego samego dnia każdego miesiąca</string>
<string name="dont_sync">Nie synchronizowane</string>
<string name="repeat_monthly_every_day_of_nth_week">każdy %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">co każde %1$s %2$s</string>
<string name="repeat_monthly_first_week">pierwszy</string>

@ -416,7 +416,6 @@
<string name="badges_description">Mostrar a contagem de tarefas no ícone do Tasks no launcher. Nem todos os launchers suportam emblemas.</string>
<string name="bundle_notifications_summary">Combinar múltiplas notificações em uma única notificação</string>
<string name="repeat_monthly_same_day_each_month">no mesmo dia cada mês</string>
<string name="dont_sync">Não sincronizar</string>
<string name="repeat_monthly_every_day_of_nth_week">todo %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">em todo %1$s %2$s</string>
<string name="repeat_monthly_first_week">primeiro</string>

@ -436,7 +436,6 @@
<string name="badges_description">Отображать количество задач на иконке приложения. Не все лаунчеры это поддерживают.</string>
<string name="bundle_notifications_summary">Группировать несколько уведомлений в одно общее</string>
<string name="repeat_monthly_same_day_each_month">в тот же день ежемесячно</string>
<string name="dont_sync">Не синхронизировать</string>
<string name="repeat_monthly_every_day_of_nth_week">каждый %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">в каждый %1$s %2$s</string>
<string name="repeat_monthly_first_week">первая</string>

@ -415,7 +415,6 @@
<string name="badges_description">Zobraziť počet úloh na ikone spustenia Úloh. Niektoré spúšťače nepodporujú odznaky.</string>
<string name="bundle_notifications_summary">Zlúč viaceré upozornenia do jedného</string>
<string name="repeat_monthly_same_day_each_month">v ten istý deň v každom mesiaci</string>
<string name="dont_sync">Nesynchronizovať </string>
<string name="repeat_monthly_every_day_of_nth_week">každý %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">na každý %1$s %2$s</string>
<string name="repeat_monthly_first_week">prvý</string>

@ -419,7 +419,6 @@
<string name="badges_description">Visa en en summering av uppgifter på Tasks ikon med ett märke. Inte alla hemskärmsappar stödjer märken.</string>
<string name="bundle_notifications_summary">Kombinera flera aviseringar i ett enda meddelande</string>
<string name="repeat_monthly_same_day_each_month">samma dag varje månad</string>
<string name="dont_sync">Synkronisera inte</string>
<string name="repeat_monthly_every_day_of_nth_week">varje %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">på varje %1$s %2$s</string>
<string name="repeat_monthly_first_week">första</string>

@ -61,7 +61,6 @@
<string name="add_account">Magdagdag ng account</string>
<string name="user">Gumagamit</string>
<string name="notification_disable_battery_optimizations_description">Mga baterya optimization ay maaaring antalahin ang mga notification</string>
<string name="dont_sync">Huwag i-sync</string>
<string name="background_sync_unmetered_only">Sa metered connections laang</string>
<string name="location_remind_departure">Ipaalala sa pagdating</string>
<string name="location_arrived">Dumating sa %s</string>

@ -421,7 +421,6 @@
<string name="badges_description">Tasks başlatıcı simgesinde görev sayısı göster. Tüm başlatıcılar rozetleri desteklemez.</string>
<string name="bundle_notifications_summary">Birden çok bildirimi tek bildirime birleştir</string>
<string name="repeat_monthly_same_day_each_month">her ayın aynı gününde</string>
<string name="dont_sync">Eşzamanlama yapma</string>
<string name="repeat_monthly_every_day_of_nth_week">her %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">her %1$s %2$s</string>
<string name="repeat_monthly_first_week">birinci</string>

@ -434,7 +434,6 @@
<string name="badges_description">Відображати кількість задач на іконці запуску Tasks. Не всі лаунчери підтримують такі значки.</string>
<string name="bundle_notifications_summary">Згрупувати декілька сповіщень в одне</string>
<string name="repeat_monthly_same_day_each_month">того ж дня щомісяця</string>
<string name="dont_sync">Не синхронізувати</string>
<string name="repeat_monthly_every_day_of_nth_week">що %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">що %1$s %2$s</string>
<string name="repeat_monthly_first_week">перший(а)</string>

@ -402,7 +402,6 @@
<string name="badges_description">在Tasks启动图标上显示任务计数。不是所有的启动器都支持角标。</string>
<string name="bundle_notifications_summary">将多个通知合并为一个通知</string>
<string name="repeat_monthly_same_day_each_month">在每月的同一天</string>
<string name="dont_sync">不同步</string>
<string name="repeat_monthly_every_day_of_nth_week">每 %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">在每 %1$s 个 %2$s</string>
<string name="repeat_monthly_first_week">第一个</string>

@ -61,6 +61,7 @@
<string name="p_tags_enabled">drawer_tags_enabled</string>
<string name="p_tags_hide_unused">drawer_tags_hide_unused</string>
<string name="p_places_enabled">drawer_places_enabled</string>
<string name="p_lists_enabled">drawer_lists_enabled</string>
<string name="p_places_hide_unused">drawer_places_hide_unused</string>
<!-- show comments in task edit -->

@ -465,7 +465,6 @@ File %1$s contained %2$s.\n\n
<string name="badges_description">Display a task count on Tasks\' launcher icon. Not all launchers support badges.</string>
<string name="bundle_notifications_summary">Combine multiple notifications into one</string>
<string name="repeat_monthly_same_day_each_month">on the same day each month</string>
<string name="dont_sync">Don\'t sync</string>
<string name="repeat_monthly_every_day_of_nth_week">every %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">on every %1$s %2$s</string>
<string name="repeat_monthly_first_week">first</string>
@ -630,4 +629,5 @@ File %1$s contained %2$s.\n\n
<string name="sort_modified_group">Modified %s</string>
<string name="on_launch">On launch</string>
<string name="open_last_viewed_list">Open last viewed list</string>
<string name="lists">Lists</string>
</resources>

@ -51,4 +51,11 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/lists">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="@string/p_lists_enabled"
android:title="@string/enabled" />
</PreferenceCategory>
</PreferenceScreen>
Loading…
Cancel
Save