From 0d5803b9caac5f7ce9b2c409d23d5a947701b3df Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 12 Jun 2024 01:55:08 -0500 Subject: [PATCH] Move filter value serialization to kmp --- .../andlib/utility/AndroidUtilities.java | 207 ------------------ .../andlib/utility/AndroidUtilities.kt | 105 +++++++++ .../com/todoroo/astrid/api/CaldavFilter.kt | 3 +- .../com/todoroo/astrid/api/GtasksFilter.kt | 3 +- .../java/com/todoroo/astrid/api/TagFilter.kt | 3 +- .../todoroo/astrid/core/CriterionInstance.kt | 9 +- .../com/todoroo/astrid/service/TaskCreator.kt | 10 +- .../activities/FilterSettingsActivity.kt | 3 +- .../tasks/filters/FilterCriteriaProvider.kt | 34 +-- .../java/org/tasks/filters/PlaceFilter.kt | 3 +- .../java/org/tasks/filters/TodayFilter.kt | 9 +- deps_fdroid.txt | 30 +-- deps_googleplay.txt | 30 +-- kmp/build.gradle.kts | 2 + .../tasks/filters/FilterValuesSerializer.kt | 82 +++++++ 15 files changed, 260 insertions(+), 273 deletions(-) delete mode 100644 app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java create mode 100644 app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.kt create mode 100644 kmp/src/commonMain/kotlin/org/tasks/filters/FilterValuesSerializer.kt diff --git a/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java b/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java deleted file mode 100644 index 9236ae14d..000000000 --- a/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.java +++ /dev/null @@ -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 - */ -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 source) { - StringBuilder result = new StringBuilder(); - for (Entry 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 mapFromSerializedString(String string) { - if (string == null) { - return new HashMap<>(); - } - - Map 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 void fromSerialized(String string, T object, SerializedPut 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 { - - void put(T object, String key, char type, String value) throws NumberFormatException; - } -} diff --git a/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.kt b/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.kt new file mode 100644 index 000000000..1cb8b7bfe --- /dev/null +++ b/app/src/main/java/com/todoroo/andlib/utility/AndroidUtilities.kt @@ -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 @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 +} diff --git a/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.kt b/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.kt index 0fd8491b1..fffecebc5 100644 --- a/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.kt +++ b/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.kt @@ -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 diff --git a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.kt b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.kt index d03f37124..d9ad4824a 100644 --- a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.kt +++ b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.kt @@ -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 diff --git a/app/src/main/java/com/todoroo/astrid/api/TagFilter.kt b/app/src/main/java/com/todoroo/astrid/api/TagFilter.kt index 3b250c7c0..67235c0ef 100644 --- a/app/src/main/java/com/todoroo/astrid/api/TagFilter.kt +++ b/app/src/main/java/com/todoroo/astrid/api/TagFilter.kt @@ -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()!! diff --git a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt index 675a1b2a3..871fc0b63 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt +++ b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt @@ -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): String { diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskCreator.kt b/app/src/main/java/com/todoroo/astrid/service/TaskCreator.kt index a82f93afa..0c47450b5 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskCreator.kt +++ b/app/src/main/java/com/todoroo/astrid/service/TaskCreator.kt @@ -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 diff --git a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt index 111800345..618af1702 100644 --- a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt +++ b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt @@ -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) } } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt index c9f48f0c2..764a6ff5b 100644 --- a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt +++ b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt @@ -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 = 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) + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/filters/PlaceFilter.kt b/app/src/main/java/org/tasks/filters/PlaceFilter.kt index 9958a3b06..91bc74eb5 100644 --- a/app/src/main/java/org/tasks/filters/PlaceFilter.kt +++ b/app/src/main/java/org/tasks/filters/PlaceFilter.kt @@ -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))) diff --git a/app/src/main/java/org/tasks/filters/TodayFilter.kt b/app/src/main/java/org/tasks/filters/TodayFilter.kt index e6bcb4560..750850a33 100644 --- a/app/src/main/java/org/tasks/filters/TodayFilter.kt +++ b/app/src/main/java/org/tasks/filters/TodayFilter.kt @@ -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 diff --git a/deps_fdroid.txt b/deps_fdroid.txt index 41a7a3ae3..16963661f 100644 --- a/deps_fdroid.txt +++ b/deps_fdroid.txt @@ -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 diff --git a/deps_googleplay.txt b/deps_googleplay.txt index 001b1dee5..9c2080c9e 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -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 diff --git a/kmp/build.gradle.kts b/kmp/build.gradle.kts index da8c4b15a..e41a0e07b 100644 --- a/kmp/build.gradle.kts +++ b/kmp/build.gradle.kts @@ -22,6 +22,8 @@ kotlin { sourceSets { commonMain.dependencies { implementation(projects.data) + implementation(compose.runtime) + implementation(libs.kermit) } } } diff --git a/kmp/src/commonMain/kotlin/org/tasks/filters/FilterValuesSerializer.kt b/kmp/src/commonMain/kotlin/org/tasks/filters/FilterValuesSerializer.kt new file mode 100644 index 000000000..fac830c7a --- /dev/null +++ b/kmp/src/commonMain/kotlin/org/tasks/filters/FilterValuesSerializer.kt @@ -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 { + 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 { + if (string == null) { + return HashMap() + } + + val result: MutableMap = 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 fromSerialized(string: String, `object`: T, putter: SerializedPut) { + 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 { + @Throws(NumberFormatException::class) + fun put(`object`: T, key: String, type: Char, value: String) +}