Move filter value serialization to kmp

pull/1926/merge
Alex Baker 2 weeks 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.filters.Filter
import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize
data class CaldavFilter(
@ -33,7 +34,7 @@ data class CaldavFilter(
)
.toString()
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
get() = calendar.order

@ -13,6 +13,7 @@ import org.tasks.data.NO_COUNT
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.filters.Filter
import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize
data class GtasksFilter(
@ -35,7 +36,7 @@ data class GtasksFilter(
.toString()
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
get() = list.order

@ -12,6 +12,7 @@ import org.tasks.data.sql.Join.Companion.inner
import org.tasks.data.sql.QueryTemplate
import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.FilterListItem
import org.tasks.filters.mapToSerializedString
@Parcelize
data class TagFilter(
@ -31,7 +32,7 @@ data class TagFilter(
get() = tagData.order
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
get() = tagData.getIcon()!!

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

@ -1,6 +1,5 @@
package com.todoroo.astrid.service
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.CaldavFilter
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.IMPORTANCE
import org.tasks.filters.Filter
import org.tasks.filters.mapFromSerializedString
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.startOfDay
@ -125,12 +125,8 @@ class TaskCreator @Inject constructor(
return create(null, title)
}
suspend fun createWithValues(filter: Filter?, title: String?): Task {
return create(
AndroidUtilities.mapFromSerializedString(filter?.valuesForNewTasks),
title
)
}
suspend fun createWithValues(filter: Filter?, title: String?): Task =
create(mapFromSerializedString(filter?.valuesForNewTasks), title)
/**
* 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.hideKeyboard
import org.tasks.filters.FilterCriteriaProvider
import org.tasks.filters.mapToSerializedString
import org.tasks.themes.CustomIcons
import java.util.Locale
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
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.CustomFilterCriterion
import com.todoroo.astrid.api.MultipleSelectCriterion
import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.api.TextInputCriterion
import com.todoroo.astrid.core.CriterionInstance
import org.tasks.data.entity.Task
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.Strings
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.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.Filter
import org.tasks.data.GoogleTask
import org.tasks.data.dao.GoogleTaskListDao
import org.tasks.data.entity.Tag
import org.tasks.data.entity.TagData
import org.tasks.data.dao.TagDataDao
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.data.entity.Task
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 javax.inject.Inject
@ -57,7 +56,7 @@ class FilterCriteriaProvider @Inject constructor(
val entries: MutableList<CriterionInstance> = java.util.ArrayList()
for (row in criterion.trim().split("\n")) {
val split = row
.split(AndroidUtilities.SERIALIZATION_SEPARATOR)
.split(SERIALIZATION_SEPARATOR)
.map { unescape(it) }
if (split.size != 4 && split.size != 5) {
Timber.e("invalid row: %s", row)
@ -415,8 +414,9 @@ class FilterCriteriaProvider @Inject constructor(
private fun unescape(item: String?): String {
return if (Strings.isNullOrEmpty(item)) {
""
} else item!!.replace(
AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR)
} else {
item!!.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR)
}
}
}
}

@ -1,7 +1,6 @@
package org.tasks.filters
import android.content.Context
import com.todoroo.andlib.utility.AndroidUtilities
import kotlinx.parcelize.Parcelize
import org.tasks.data.NO_COUNT
import org.tasks.data.dao.TaskDao.TaskCriteria.activeAndVisible
@ -22,7 +21,7 @@ data class PlaceFilter(
override val count: Int = NO_COUNT,
) : Filter {
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
get() = QueryTemplate()
.join(inner(G2, Task.ID.eq(G2_TASK)))

@ -1,12 +1,11 @@
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 org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize
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
@Parcelize
@ -28,7 +27,7 @@ data class TodayFilter(
get() = CustomIcons.TODAY
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 {
return other is TodayFilter

@ -35,8 +35,6 @@
+| | | | +--- 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: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: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.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | +--- 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-android: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-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
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.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.activity:activity-compose:1.5.0 -> 1.9.0
+| | +--- androidx.activity:activity-ktx:1.9.0 (*)
+| | +--- 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: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.annotation:annotation:1.1.0 -> 1.8.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (*)
+| | | +--- 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-livedata:1.6.7 (c)
+| | +--- 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: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: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.kotlin:kotlin-stdlib:1.9.22 -> 2.0.0 (*)
+| | +--- 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-android: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-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
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20 -> 2.0.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.activity:activity-compose:1.5.0 -> 1.9.0
+| | +--- androidx.activity:activity-ktx:1.9.0 (*)
+| | +--- 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: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.annotation:annotation:1.1.0 -> 1.8.0 (*)
+| | | +--- androidx.compose.runtime:runtime:1.6.7 (*)
+| | | +--- 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-livedata:1.6.7 (c)
+| | +--- androidx.compose.ui:ui:1.0.1 -> 1.6.7

@ -22,6 +22,8 @@ kotlin {
sourceSets {
commonMain.dependencies {
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