Move filter value serialization to kmp

pull/2912/head
Alex Baker 4 months ago
parent bbaaf27386
commit 0d5803b9ca

@ -1,207 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Looper;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.widget.TextView;
import org.tasks.BuildConfig;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import timber.log.Timber;
/**
* Android Utility Classes
*
* @author Tim Su <tim@todoroo.com>
*/
public class AndroidUtilities {
public static final String SEPARATOR_ESCAPE = "!PIPE!"; // $NON-NLS-1$
public static final String SERIALIZATION_SEPARATOR = "|"; // $NON-NLS-1$
// --- utility methods
/** Suppress virtual keyboard until user's first tap */
public static void suppressVirtualKeyboard(final TextView editor) {
final int inputType = editor.getInputType();
editor.setInputType(InputType.TYPE_NULL);
editor.setOnTouchListener(
(v, event) -> {
editor.setInputType(inputType);
editor.setOnTouchListener(null);
return false;
});
}
// --- serialization
/** Serializes a content value into a string */
public static String mapToSerializedString(Map<String, Object> source) {
StringBuilder result = new StringBuilder();
for (Entry<String, Object> entry : source.entrySet()) {
addSerialized(result, entry.getKey(), entry.getValue());
}
return result.toString();
}
/** add serialized helper */
private static void addSerialized(StringBuilder result, String key, Object value) {
result
.append(key.replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE))
.append(SERIALIZATION_SEPARATOR);
if (value instanceof Integer) {
result.append('i').append(value);
} else if (value instanceof Double) {
result.append('d').append(value);
} else if (value instanceof Long) {
result.append('l').append(value);
} else if (value instanceof String) {
result
.append('s')
.append(value.toString().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE));
} else if (value instanceof Boolean) {
result.append('b').append(value);
} else {
throw new UnsupportedOperationException(value.getClass().toString());
}
result.append(SERIALIZATION_SEPARATOR);
}
public static Map<String, Serializable> mapFromSerializedString(String string) {
if (string == null) {
return new HashMap<>();
}
Map<String, Serializable> result = new HashMap<>();
fromSerialized(
string,
result,
(object, key, type, value) -> {
switch (type) {
case 'i':
object.put(key, Integer.parseInt(value));
break;
case 'd':
object.put(key, Double.parseDouble(value));
break;
case 'l':
object.put(key, Long.parseLong(value));
break;
case 's':
object.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
break;
case 'b':
object.put(key, Boolean.parseBoolean(value));
break;
}
});
return result;
}
private static <T> void fromSerialized(String string, T object, SerializedPut<T> putter) {
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); // $NON-NLS-1$
for (int i = 0; i < pairs.length; i += 2) {
try {
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
String value = pairs[i + 1].substring(1);
try {
putter.put(object, key, pairs[i + 1].charAt(0), value);
} catch (NumberFormatException e) {
// failed parse to number
putter.put(object, key, 's', value);
Timber.e(e);
}
} catch (IndexOutOfBoundsException e) {
Timber.e(e);
}
}
}
public static int convertDpToPixels(DisplayMetrics displayMetrics, int dp) {
// developer.android.com/guide/practices/screens_support.html#dips-pels
return (int) (dp * displayMetrics.density + 0.5f);
}
public static boolean preOreo() {
return !atLeastOreo();
}
public static boolean preS() {
return !atLeastS();
}
public static boolean preTiramisu() {
return !atLeastTiramisu();
}
public static boolean preUpsideDownCake() {
return VERSION.SDK_INT <= VERSION_CODES.TIRAMISU;
}
public static boolean atLeastNougatMR1() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
}
public static boolean atLeastOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public static boolean atLeastOreoMR1() {
return Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1;
}
public static boolean atLeastP() {
return VERSION.SDK_INT >= Build.VERSION_CODES.P;
}
public static boolean atLeastQ() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
}
public static boolean atLeastR() {
return VERSION.SDK_INT >= VERSION_CODES.R;
}
public static boolean atLeastS() {
return VERSION.SDK_INT >= VERSION_CODES.S;
}
public static boolean atLeastTiramisu() {
return VERSION.SDK_INT >= VERSION_CODES.TIRAMISU;
}
public static void assertMainThread() {
if (BuildConfig.DEBUG && !isMainThread()) {
throw new IllegalStateException("Should be called from main thread");
}
}
public static void assertNotMainThread() {
if (BuildConfig.DEBUG && isMainThread()) {
throw new IllegalStateException("Should not be called from main thread");
}
}
private static boolean isMainThread() {
return Thread.currentThread() == Looper.getMainLooper().getThread();
}
interface SerializedPut<T> {
void put(T object, String key, char type, String value) throws NumberFormatException;
}
}

@ -0,0 +1,105 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Looper
import android.text.InputType
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import org.tasks.BuildConfig
/**
* Android Utility Classes
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
object AndroidUtilities {
// --- utility methods
/** Suppress virtual keyboard until user's first tap */
@JvmStatic
fun suppressVirtualKeyboard(editor: TextView) {
val inputType = editor.inputType
editor.inputType = InputType.TYPE_NULL
editor.setOnTouchListener { v: View?, event: MotionEvent? ->
editor.inputType = inputType
editor.setOnTouchListener(null)
false
}
}
fun convertDpToPixels(displayMetrics: DisplayMetrics, dp: Int): Int {
// developer.android.com/guide/practices/screens_support.html#dips-pels
return (dp * displayMetrics.density + 0.5f).toInt()
}
fun preOreo(): Boolean {
return !atLeastOreo()
}
fun preS(): Boolean {
return !atLeastS()
}
@JvmStatic
fun preTiramisu(): Boolean {
return !atLeastTiramisu()
}
fun preUpsideDownCake(): Boolean {
return Build.VERSION.SDK_INT <= VERSION_CODES.TIRAMISU
}
fun atLeastNougatMR1(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.N_MR1
}
@JvmStatic
fun atLeastOreo(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.O
}
fun atLeastOreoMR1(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.O_MR1
}
fun atLeastP(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.P
}
@JvmStatic
fun atLeastQ(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.Q
}
@JvmStatic
fun atLeastR(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.R
}
@JvmStatic
fun atLeastS(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.S
}
fun atLeastTiramisu(): Boolean {
return Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU
}
fun assertMainThread() {
check(!(BuildConfig.DEBUG && !isMainThread)) { "Should be called from main thread" }
}
fun assertNotMainThread() {
check(!(BuildConfig.DEBUG && isMainThread)) { "Should not be called from main thread" }
}
private val isMainThread: Boolean
get() = Thread.currentThread() === Looper.getMainLooper().thread
}

@ -12,6 +12,7 @@ import org.tasks.data.entity.Task
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.filters.Filter import org.tasks.filters.Filter
import org.tasks.filters.FilterListItem import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize @Parcelize
data class CaldavFilter( data class CaldavFilter(
@ -33,7 +34,7 @@ data class CaldavFilter(
) )
.toString() .toString()
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(CaldavTask.KEY to calendar.uuid!!)) get() = mapToSerializedString(mapOf(CaldavTask.KEY to calendar.uuid!!))
override val order: Int override val order: Int
get() = calendar.order get() = calendar.order

@ -13,6 +13,7 @@ import org.tasks.data.NO_COUNT
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.filters.Filter import org.tasks.filters.Filter
import org.tasks.filters.FilterListItem import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize @Parcelize
data class GtasksFilter( data class GtasksFilter(
@ -35,7 +36,7 @@ data class GtasksFilter(
.toString() .toString()
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(GoogleTask.KEY to list.uuid!!)) get() = mapToSerializedString(mapOf(GoogleTask.KEY to list.uuid!!))
override val order: Int override val order: Int
get() = list.order get() = list.order

@ -12,6 +12,7 @@ import org.tasks.data.sql.Join.Companion.inner
import org.tasks.data.sql.QueryTemplate import org.tasks.data.sql.QueryTemplate
import org.tasks.filters.AstridOrderingFilter import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.FilterListItem import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize @Parcelize
data class TagFilter( data class TagFilter(
@ -31,7 +32,7 @@ data class TagFilter(
get() = tagData.order get() = tagData.order
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(Tag.KEY to tagData.name!!)) get() = mapToSerializedString(mapOf(Tag.KEY to tagData.name!!))
override val icon: Int override val icon: Int
get() = tagData.getIcon()!! get() = tagData.getIcon()!!

@ -1,11 +1,12 @@
package com.todoroo.astrid.core package com.todoroo.astrid.core
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.api.BooleanCriterion import com.todoroo.astrid.api.BooleanCriterion
import com.todoroo.astrid.api.CustomFilterCriterion import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.api.MultipleSelectCriterion import com.todoroo.astrid.api.MultipleSelectCriterion
import com.todoroo.astrid.api.TextInputCriterion import com.todoroo.astrid.api.TextInputCriterion
import org.tasks.data.UUIDHelper import org.tasks.data.UUIDHelper
import org.tasks.filters.SEPARATOR_ESCAPE
import org.tasks.filters.SERIALIZATION_SEPARATOR
class CriterionInstance { class CriterionInstance {
lateinit var criterion: CustomFilterCriterion lateinit var criterion: CustomFilterCriterion
@ -90,7 +91,7 @@ class CriterionInstance {
escape(criterion.text), escape(criterion.text),
type, type,
criterion.sql ?: "") criterion.sql ?: "")
.joinToString(AndroidUtilities.SERIALIZATION_SEPARATOR) .joinToString(SERIALIZATION_SEPARATOR)
} }
override fun toString(): String { override fun toString(): String {
@ -132,9 +133,7 @@ class CriterionInstance {
const val TYPE_UNIVERSE = 3 const val TYPE_UNIVERSE = 3
private fun escape(item: String?): String { private fun escape(item: String?): String {
return item?.replace( return item?.replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE) ?: ""
AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE)
?: "" // $NON-NLS-1$
} }
fun serialize(criterion: List<CriterionInstance>): String { fun serialize(criterion: List<CriterionInstance>): String {

@ -1,6 +1,5 @@
package com.todoroo.astrid.service package com.todoroo.astrid.service
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.CaldavFilter import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.api.GtasksFilter
@ -36,6 +35,7 @@ import org.tasks.data.entity.Task.Companion.HIDE_UNTIL
import org.tasks.data.entity.Task.Companion.HIDE_UNTIL_NONE import org.tasks.data.entity.Task.Companion.HIDE_UNTIL_NONE
import org.tasks.data.entity.Task.Companion.IMPORTANCE import org.tasks.data.entity.Task.Companion.IMPORTANCE
import org.tasks.filters.Filter import org.tasks.filters.Filter
import org.tasks.filters.mapFromSerializedString
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
@ -125,12 +125,8 @@ class TaskCreator @Inject constructor(
return create(null, title) return create(null, title)
} }
suspend fun createWithValues(filter: Filter?, title: String?): Task { suspend fun createWithValues(filter: Filter?, title: String?): Task =
return create( create(mapFromSerializedString(filter?.valuesForNewTasks), title)
AndroidUtilities.mapFromSerializedString(filter?.valuesForNewTasks),
title
)
}
/** /**
* Create task from the given content values, saving it. This version doesn't need to start with a * Create task from the given content values, saving it. This version doesn't need to start with a

@ -48,6 +48,7 @@ import org.tasks.extensions.Context.hideKeyboard
import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.openUri
import org.tasks.extensions.hideKeyboard import org.tasks.extensions.hideKeyboard
import org.tasks.filters.FilterCriteriaProvider import org.tasks.filters.FilterCriteriaProvider
import org.tasks.filters.mapToSerializedString
import org.tasks.themes.CustomIcons import org.tasks.themes.CustomIcons
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -399,7 +400,7 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
} }
} }
} }
return AndroidUtilities.mapToSerializedString(values) return mapToSerializedString(values)
} }
} }
} }

@ -1,35 +1,34 @@
package org.tasks.filters package org.tasks.filters
import android.content.Context import android.content.Context
import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Criterion.Companion.exists
import org.tasks.data.sql.Criterion.Companion.or
import org.tasks.data.sql.Field.Companion.field
import org.tasks.data.sql.Join.Companion.inner
import org.tasks.data.sql.Query.Companion.select
import org.tasks.data.sql.UnaryCriterion.Companion.isNotNull
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.api.BooleanCriterion import com.todoroo.astrid.api.BooleanCriterion
import com.todoroo.astrid.api.CustomFilterCriterion import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.api.MultipleSelectCriterion import com.todoroo.astrid.api.MultipleSelectCriterion
import com.todoroo.astrid.api.PermaSql import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.api.TextInputCriterion import com.todoroo.astrid.api.TextInputCriterion
import com.todoroo.astrid.core.CriterionInstance import com.todoroo.astrid.core.CriterionInstance
import org.tasks.data.entity.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R import org.tasks.R
import org.tasks.Strings import org.tasks.Strings
import org.tasks.activities.FilterSettingsActivity.Companion.sql import org.tasks.activities.FilterSettingsActivity.Companion.sql
import org.tasks.data.entity.Alarm import org.tasks.data.GoogleTask
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.dao.TagDataDao
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.CaldavTask import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Filter import org.tasks.data.entity.Filter
import org.tasks.data.GoogleTask
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.entity.Tag import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData import org.tasks.data.entity.TagData
import org.tasks.data.dao.TagDataDao import org.tasks.data.entity.Task
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Criterion.Companion.exists
import org.tasks.data.sql.Criterion.Companion.or
import org.tasks.data.sql.Field.Companion.field
import org.tasks.data.sql.Join.Companion.inner
import org.tasks.data.sql.Query.Companion.select
import org.tasks.data.sql.UnaryCriterion.Companion.isNotNull
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -57,7 +56,7 @@ class FilterCriteriaProvider @Inject constructor(
val entries: MutableList<CriterionInstance> = java.util.ArrayList() val entries: MutableList<CriterionInstance> = java.util.ArrayList()
for (row in criterion.trim().split("\n")) { for (row in criterion.trim().split("\n")) {
val split = row val split = row
.split(AndroidUtilities.SERIALIZATION_SEPARATOR) .split(SERIALIZATION_SEPARATOR)
.map { unescape(it) } .map { unescape(it) }
if (split.size != 4 && split.size != 5) { if (split.size != 4 && split.size != 5) {
Timber.e("invalid row: %s", row) Timber.e("invalid row: %s", row)
@ -415,8 +414,9 @@ class FilterCriteriaProvider @Inject constructor(
private fun unescape(item: String?): String { private fun unescape(item: String?): String {
return if (Strings.isNullOrEmpty(item)) { return if (Strings.isNullOrEmpty(item)) {
"" ""
} else item!!.replace( } else {
AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR) item!!.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR)
}
} }
} }
} }

@ -1,7 +1,6 @@
package org.tasks.filters package org.tasks.filters
import android.content.Context import android.content.Context
import com.todoroo.andlib.utility.AndroidUtilities
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.tasks.data.NO_COUNT import org.tasks.data.NO_COUNT
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
@ -22,7 +21,7 @@ data class PlaceFilter(
override val count: Int = NO_COUNT, override val count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(Place.KEY to place.uid!!)) get() = mapToSerializedString(mapOf(Place.KEY to place.uid!!))
override val sql: String override val sql: String
get() = QueryTemplate() get() = QueryTemplate()
.join(inner(G2, Task.ID.eq(G2_TASK))) .join(inner(G2, Task.ID.eq(G2_TASK)))

@ -1,12 +1,11 @@
package org.tasks.filters package org.tasks.filters
import org.tasks.data.sql.Criterion
import org.tasks.data.sql.QueryTemplate
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.api.PermaSql import com.todoroo.astrid.api.PermaSql
import org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.tasks.data.dao.TaskDao import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.Task
import org.tasks.data.sql.Criterion
import org.tasks.data.sql.QueryTemplate
import org.tasks.themes.CustomIcons import org.tasks.themes.CustomIcons
@Parcelize @Parcelize
@ -28,7 +27,7 @@ data class TodayFilter(
get() = CustomIcons.TODAY get() = CustomIcons.TODAY
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(Task.DUE_DATE.name to PermaSql.VALUE_NOON)) get() = mapToSerializedString(mapOf(Task.DUE_DATE.name to PermaSql.VALUE_NOON))
override fun areItemsTheSame(other: FilterListItem): Boolean { override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is TodayFilter return other is TodayFilter

@ -35,8 +35,6 @@
+| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 (c) +| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 (c)
+| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 (c) +| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 (c)
+| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 (c) +| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 (c)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 -> 2.0.0
+| | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -> 2.0.0 +| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -> 2.0.0
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.0 +| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.0
@ -262,7 +260,8 @@
+| | \--- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3 +| | \--- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 -> 2.0.0 (*) +| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 -> 2.0.0
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| +--- co.touchlab:kermit:2.0.3 +| +--- co.touchlab:kermit:2.0.3
+| | \--- co.touchlab:kermit-android:2.0.3 +| | \--- co.touchlab:kermit-android:2.0.3
+| | +--- co.touchlab:kermit-core:2.0.3 +| | +--- co.touchlab:kermit-core:2.0.3
@ -270,6 +269,20 @@
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*) +| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*)
++--- project :kmp
+| +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| +--- project :data (*)
+| +--- org.jetbrains.compose.runtime:runtime:1.6.10
+| | \--- androidx.compose.runtime:runtime:1.6.7
+| | \--- androidx.compose.runtime:runtime-android:1.6.7
+| | +--- androidx.collection:collection:1.4.0 (*)
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.7.3 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 -> 1.7.3 (*)
+| | +--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | \--- androidx.compose.runtime:runtime-saveable:1.6.7 (c)
+| +--- co.touchlab:kermit:2.0.3 (*)
+| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*)
++--- com.github.bitfireAT:dav4jvm:2.2.1 ++--- com.github.bitfireAT:dav4jvm:2.2.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.0 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.0 (*)
+| +--- org.apache.commons:commons-lang3:3.8.1 -> 3.12.0 +| +--- org.apache.commons:commons-lang3:3.8.1 -> 3.12.0
@ -643,21 +656,12 @@
+| \--- androidx.compose.material3:material3-android:1.2.1 +| \--- androidx.compose.material3:material3-android:1.2.1
+| +--- androidx.activity:activity-compose:1.5.0 -> 1.9.0 +| +--- androidx.activity:activity-compose:1.5.0 -> 1.9.0
+| | +--- androidx.activity:activity-ktx:1.9.0 (*) +| | +--- androidx.activity:activity-ktx:1.9.0 (*)
+| | +--- androidx.compose.runtime:runtime:1.0.1 -> 1.6.7 +| | +--- androidx.compose.runtime:runtime:1.0.1 -> 1.6.7 (*)
+| | | \--- androidx.compose.runtime:runtime-android:1.6.7
+| | | +--- androidx.collection:collection:1.4.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.7.3 (*)
+| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 -> 1.7.3 (*)
+| | | +--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | | \--- androidx.compose.runtime:runtime-saveable:1.6.7 (c)
+| | +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.6.7 +| | +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.6.7
+| | | \--- androidx.compose.runtime:runtime-saveable-android:1.6.7 +| | | \--- androidx.compose.runtime:runtime-saveable-android:1.6.7
+| | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*) +| | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (*) +| | | +--- androidx.compose.runtime:runtime:1.6.7 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 -> 2.0.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (c) +| | | +--- androidx.compose.runtime:runtime:1.6.7 (c)
+| | | \--- androidx.compose.runtime:runtime-livedata:1.6.7 (c) +| | | \--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | +--- androidx.compose.ui:ui:1.0.1 -> 1.6.7 +| | +--- androidx.compose.ui:ui:1.0.1 -> 1.6.7

@ -36,8 +36,6 @@
+| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 (c) +| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 (c)
+| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 (c) +| | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 (c)
+| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3 (c) +| | | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3 (c)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.20 -> 2.0.0
+| | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -> 2.0.0 +| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20 -> 2.0.0
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.0 +| | | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.0
@ -625,7 +623,8 @@
+| | \--- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3 +| | \--- org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3 (*) +| | +--- org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 -> 2.0.0 (*) +| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 -> 2.0.0
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| +--- co.touchlab:kermit:2.0.3 +| +--- co.touchlab:kermit:2.0.3
+| | \--- co.touchlab:kermit-android:2.0.3 +| | \--- co.touchlab:kermit-android:2.0.3
+| | +--- co.touchlab:kermit-core:2.0.3 +| | +--- co.touchlab:kermit-core:2.0.3
@ -633,6 +632,20 @@
+| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*) +| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*) +| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*)
++--- project :kmp
+| +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.0 (*)
+| +--- project :data (*)
+| +--- org.jetbrains.compose.runtime:runtime:1.6.10
+| | \--- androidx.compose.runtime:runtime:1.6.7
+| | \--- androidx.compose.runtime:runtime-android:1.6.7
+| | +--- androidx.collection:collection:1.4.0 (*)
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.7.3 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 -> 1.7.3 (*)
+| | +--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | \--- androidx.compose.runtime:runtime-saveable:1.6.7 (c)
+| +--- co.touchlab:kermit:2.0.3 (*)
+| \--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.0 (*)
++--- com.github.bitfireAT:dav4jvm:2.2.1 ++--- com.github.bitfireAT:dav4jvm:2.2.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.0 (*) +| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.0 (*)
+| +--- org.apache.commons:commons-lang3:3.8.1 -> 3.12.0 +| +--- org.apache.commons:commons-lang3:3.8.1 -> 3.12.0
@ -874,21 +887,12 @@
+| \--- androidx.compose.material3:material3-android:1.2.1 +| \--- androidx.compose.material3:material3-android:1.2.1
+| +--- androidx.activity:activity-compose:1.5.0 -> 1.9.0 +| +--- androidx.activity:activity-compose:1.5.0 -> 1.9.0
+| | +--- androidx.activity:activity-ktx:1.9.0 (*) +| | +--- androidx.activity:activity-ktx:1.9.0 (*)
+| | +--- androidx.compose.runtime:runtime:1.0.1 -> 1.6.7 +| | +--- androidx.compose.runtime:runtime:1.0.1 -> 1.6.7 (*)
+| | | \--- androidx.compose.runtime:runtime-android:1.6.7
+| | | +--- androidx.collection:collection:1.4.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -> 1.7.3 (*)
+| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 -> 1.7.3 (*)
+| | | +--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | | \--- androidx.compose.runtime:runtime-saveable:1.6.7 (c)
+| | +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.6.7 +| | +--- androidx.compose.runtime:runtime-saveable:1.0.1 -> 1.6.7
+| | | \--- androidx.compose.runtime:runtime-saveable-android:1.6.7 +| | | \--- androidx.compose.runtime:runtime-saveable-android:1.6.7
+| | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*) +| | | +--- androidx.annotation:annotation:1.1.0 -> 1.8.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (*) +| | | +--- androidx.compose.runtime:runtime:1.6.7 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*) +| | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.0 (*)
+| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22 -> 2.0.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (c) +| | | +--- androidx.compose.runtime:runtime:1.6.7 (c)
+| | | \--- androidx.compose.runtime:runtime-livedata:1.6.7 (c) +| | | \--- androidx.compose.runtime:runtime-livedata:1.6.7 (c)
+| | +--- androidx.compose.ui:ui:1.0.1 -> 1.6.7 +| | +--- androidx.compose.ui:ui:1.0.1 -> 1.6.7

@ -22,6 +22,8 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation(projects.data) implementation(projects.data)
implementation(compose.runtime)
implementation(libs.kermit)
} }
} }
} }

@ -0,0 +1,82 @@
package org.tasks.filters
import co.touchlab.kermit.Logger
const val SEPARATOR_ESCAPE: String = "!PIPE!"
const val SERIALIZATION_SEPARATOR: String = "|"
fun mapToSerializedString(source: Map<String, Any>): String {
val result = StringBuilder()
for ((key, value) in source) {
addSerialized(result, key, value)
}
return result.toString()
}
private fun addSerialized(result: StringBuilder, key: String, value: Any) {
result
.append(key.replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE))
.append(SERIALIZATION_SEPARATOR)
if (value is Int) {
result.append('i').append(value)
} else if (value is Double) {
result.append('d').append(value)
} else if (value is Long) {
result.append('l').append(value)
} else if (value is String) {
result
.append('s')
.append(value.toString().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE))
} else if (value is Boolean) {
result.append('b').append(value)
} else {
throw UnsupportedOperationException(value.javaClass.toString())
}
result.append(SERIALIZATION_SEPARATOR)
}
fun mapFromSerializedString(string: String?): Map<String, Any> {
if (string == null) {
return HashMap()
}
val result: MutableMap<String, Any> = HashMap()
fromSerialized(string, result) { `object`, key, type, value ->
when (type) {
'i' -> `object`[key] = value.toInt()
'd' -> `object`[key] = value.toDouble()
'l' -> `object`[key] = value.toLong()
's' -> `object`[key] = value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR)
'b' -> `object`[key] = value.toBoolean()
}
}
return result
}
private fun <T> fromSerialized(string: String, `object`: T, putter: SerializedPut<T>) {
val pairs =
string.split(("\\" + SERIALIZATION_SEPARATOR).toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
var i = 0
while (i < pairs.size) {
try {
val key = pairs[i].replace(SEPARATOR_ESCAPE.toRegex(), SERIALIZATION_SEPARATOR)
val value = pairs[i + 1].substring(1)
try {
putter.put(`object`, key, pairs[i + 1][0], value)
} catch (e: NumberFormatException) {
// failed parse to number
putter.put(`object`, key, 's', value)
Logger.e(e) { e.message ?: "NumberFormatException" }
}
} catch (e: IndexOutOfBoundsException) {
Logger.e(e) { e.message ?: "IndexOutOfBoundsException" }
}
i += 2
}
}
internal fun interface SerializedPut<T> {
@Throws(NumberFormatException::class)
fun put(`object`: T, key: String, type: Char, value: String)
}
Loading…
Cancel
Save