diff --git a/CHANGELOG.md b/CHANGELOG.md index e0b6b62a..f576b0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +## Version 5.4.4 + +_2022-03-26_ + +* Compile with SDK 32. +* Update dependencies to stable version. +* Switch to Material3 theme. +* Replace FuzzySearch library with Levenshtein algorithm implementation. + ## Version 5.4.3 _2021-12-29_ @@ -12,7 +21,7 @@ _2021-12-29_ _2021-12-24_ * Update to Kotlin 1.6.10. -* Update Corooutines to 1.6.0. +* Update Coroutines to 1.6.0. * Update Gradle wrapper to 7.3.3. * Refactor edit database activity to rename database dialog. diff --git a/README.md b/README.md index 11ee72c6..e4ab7fdc 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ Then add the following dependencies in your app `build.gradle` or `build.gradle. **Groovy** ```groovy -debugImplementation "com.infinum.dbinspector:dbinspector:5.4.3" -releaseImplementation "com.infinum.dbinspector:dbinspector-no-op:5.4.3" +debugImplementation "com.infinum.dbinspector:dbinspector:5.4.4" +releaseImplementation "com.infinum.dbinspector:dbinspector-no-op:5.4.4" ``` **KotlinDSL** ```kotlin -debugImplementation("com.infinum.dbinspector:dbinspector:5.4.3") -releaseImplementation("com.infinum.dbinspector:dbinspector-no-op:5.4.3") +debugImplementation("com.infinum.dbinspector:dbinspector:5.4.4") +releaseImplementation("com.infinum.dbinspector:dbinspector-no-op:5.4.4") ``` ### Usage diff --git a/config.gradle b/config.gradle index 8ee4a5f2..a149e4b2 100644 --- a/config.gradle +++ b/config.gradle @@ -1,13 +1,13 @@ ext { buildConfig = [ "minSdk" : 21, - "compileSdk": 31, - "targetSdk" : 31, - "buildTools": "31.0.0" + "compileSdk": 32, + "targetSdk" : 32, + "buildTools": "32.0.0" ] releaseConfig = [ "group" : "com.infinum.dbinspector", - "version" : "5.4.3", - "versionCode": 5 * 100 * 100 + 4 * 100 + 3 + "version" : "5.4.4", + "versionCode": 5 * 100 * 100 + 4 * 100 + 4 ] } \ No newline at end of file diff --git a/dbinspector/build.gradle b/dbinspector/build.gradle index f567b7ba..5bb5518d 100644 --- a/dbinspector/build.gradle +++ b/dbinspector/build.gradle @@ -100,7 +100,6 @@ dependencies { implementation libs.protobuf.javalite implementation libs.material implementation libs.koin.android - implementation libs.fuzzy testImplementation libs.bundles.test } diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/DbInspector.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/DbInspector.kt index cf5a889c..cbb0fb2e 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/DbInspector.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/DbInspector.kt @@ -1,7 +1,6 @@ package com.infinum.dbinspector import android.content.Intent -import com.infinum.dbinspector.data.sources.memory.logger.EmptyLogger import com.infinum.dbinspector.data.sources.memory.logger.Logger import com.infinum.dbinspector.ui.Presentation import com.infinum.dbinspector.ui.databases.DatabasesActivity diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Data.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Data.kt index 856f80c2..528cb513 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Data.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Data.kt @@ -15,6 +15,7 @@ import com.infinum.dbinspector.data.sources.local.proto.history.HistorySerialize import com.infinum.dbinspector.data.sources.local.proto.settings.SettingsDataStore import com.infinum.dbinspector.data.sources.local.proto.settings.SettingsSerializer import com.infinum.dbinspector.data.sources.memory.connection.AndroidConnectionSource +import com.infinum.dbinspector.data.sources.memory.distance.LevenshteinDistance import com.infinum.dbinspector.data.sources.memory.pagination.CursorPaginator import com.infinum.dbinspector.data.sources.memory.pagination.Paginator import com.infinum.dbinspector.data.sources.raw.AndroidDatabasesSource @@ -102,7 +103,9 @@ internal object Data { factory(qualifier = Qualifiers.Schema.RAW_QUERY) { CursorPaginator() } - single { AndroidConnectionSource() } + single { AndroidConnectionSource() } + + single { LevenshteinDistance() } } private fun local() = module { diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Sources.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Sources.kt index 003539d1..7e71f3a2 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Sources.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/Sources.kt @@ -26,9 +26,17 @@ internal interface Sources { interface Memory { - suspend fun openConnection(path: String): SQLiteDatabase + interface Connection { - suspend fun closeConnection(path: String) + suspend fun openConnection(path: String): SQLiteDatabase + + suspend fun closeConnection(path: String) + } + + interface Distance { + + suspend fun calculate(query: String, options: List): Int? + } } interface Local { diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/connection/AndroidConnectionSource.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/connection/AndroidConnectionSource.kt index 2936ac70..024a3253 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/connection/AndroidConnectionSource.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/connection/AndroidConnectionSource.kt @@ -4,7 +4,7 @@ import android.database.sqlite.SQLiteDatabase import androidx.annotation.VisibleForTesting import com.infinum.dbinspector.data.Sources -internal class AndroidConnectionSource : Sources.Memory { +internal class AndroidConnectionSource : Sources.Memory.Connection { @VisibleForTesting internal val connectionPool: HashMap = hashMapOf() diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/distance/LevenshteinDistance.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/distance/LevenshteinDistance.kt new file mode 100644 index 00000000..126c8db5 --- /dev/null +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/data/sources/memory/distance/LevenshteinDistance.kt @@ -0,0 +1,89 @@ +package com.infinum.dbinspector.data.sources.memory.distance + +import com.infinum.dbinspector.data.Sources +import kotlin.math.min + +/** + * Algorithm for measuring the difference between two Strings, also called a distance. + * + * It is the number of changes needed to change one String into another, + * where each change is a single character modification. + * + */ +internal class LevenshteinDistance : Sources.Memory.Distance { + + /** + * Iterates over options and calculates unlimited distance between each option and query String. + * Maps a tuple of index, option, and distance, then finds minimum distance tuple and returns index. + * + * @return result index of option with minimum distance. + */ + override suspend fun calculate(query: String, options: List): Int? = + options.mapIndexed { index, option -> + Triple(index, option, calculateUnlimited(query, option)) + } + .minByOrNull { it.third } + ?.first + + /** + * Calculates Levenshtein distance between two Strings without a threshold to pass. + * A higher score indicates a greater distance. + * + * calculateUnlimited("scar", "car") = 1 + * calculateUnlimited("car", "mug") = 3 + * + * @return result distance. + */ + @Suppress("NestedBlockDepth", "ReturnCount") + private fun calculateUnlimited( + query: String, + other: String + ): Int { + var left: String = query + var right: String = other + + var leftLength = left.length + var rightLength = right.length + when { + leftLength == 0 -> return rightLength + rightLength == 0 -> return leftLength + else -> { + if (leftLength > rightLength) { + val tmp: String = left + left = right + right = tmp + leftLength = rightLength + rightLength = right.length + } + val previous = IntArray(leftLength + 1) + + var i: Int + var j = 1 + var upperLeft: Int + var upper: Int + var rightJ: Char + var cost: Int + i = 0 + while (i <= leftLength) { + previous[i] = i + i++ + } + while (j <= rightLength) { + upperLeft = previous[0] + rightJ = right[j - 1] + previous[0] = j + i = 1 + while (i <= leftLength) { + upper = previous[i] + cost = if (left[i - 1] == rightJ) 0 else 1 + previous[i] = min(min(previous[i - 1] + 1, previous[i] + 1), upperLeft + cost) + upperLeft = upper + i++ + } + j++ + } + return previous[leftLength] + } + } + } +} diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/Domain.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/Domain.kt index 97457ae9..36defe1e 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/Domain.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/Domain.kt @@ -274,7 +274,7 @@ internal object Domain { factory { SaveExecutionInteractor(get()) } factory { ClearHistoryInteractor(get()) } factory { RemoveExecutionInteractor(get()) } - factory { GetExecutionInteractor(get()) } + factory { GetExecutionInteractor(get(), get()) } factory { ExecutionMapper() } factory { HistoryMapper(get()) } diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractor.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractor.kt index 002a076a..2248c5da 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractor.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractor.kt @@ -4,7 +4,7 @@ import com.infinum.dbinspector.data.Sources import com.infinum.dbinspector.domain.Interactors internal class CloseConnectionInteractor( - private val source: Sources.Memory + private val source: Sources.Memory.Connection ) : Interactors.CloseConnection { override suspend fun invoke(input: String) = diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractor.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractor.kt index 9347be2d..9d561f6a 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractor.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractor.kt @@ -5,7 +5,7 @@ import com.infinum.dbinspector.data.Sources import com.infinum.dbinspector.domain.Interactors internal class OpenConnectionInteractor( - private val source: Sources.Memory + private val source: Sources.Memory.Connection ) : Interactors.OpenConnection { override suspend fun invoke(input: String): SQLiteDatabase = diff --git a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/history/interactors/GetExecutionInteractor.kt b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/history/interactors/GetExecutionInteractor.kt index 82ef41c0..6adfbdab 100644 --- a/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/history/interactors/GetExecutionInteractor.kt +++ b/dbinspector/src/main/kotlin/com/infinum/dbinspector/domain/history/interactors/GetExecutionInteractor.kt @@ -4,10 +4,10 @@ import com.infinum.dbinspector.data.Sources import com.infinum.dbinspector.data.models.local.proto.input.HistoryTask import com.infinum.dbinspector.data.models.local.proto.output.HistoryEntity import com.infinum.dbinspector.domain.Interactors -import me.xdrop.fuzzywuzzy.FuzzySearch internal class GetExecutionInteractor( - private val dataStore: Sources.Local.History + private val dataStore: Sources.Local.History, + private val distance: Sources.Memory.Distance ) : Interactors.GetExecution { override suspend fun invoke(input: HistoryTask): HistoryEntity = @@ -15,13 +15,11 @@ internal class GetExecutionInteractor( .executionsList .filter { it.databasePath == input.execution?.databasePath } .takeIf { it.isNotEmpty() } - ?.let { entities -> - FuzzySearch.extractOne( + ?.let { entities: List -> + distance.calculate( input.execution?.execution.orEmpty(), - entities.map { it.execution } - ) - ?.takeIf { it.score != 0 } - ?.let { entities[it.index] } + entities.map { it.execution }, + )?.let { index -> entities[index] } } ?.let { HistoryEntity.getDefaultInstance() diff --git a/dbinspector/src/main/res/layout-land/dbinspector_activity_edit.xml b/dbinspector/src/main/res/layout-land/dbinspector_activity_edit.xml index 78716698..7c8273eb 100644 --- a/dbinspector/src/main/res/layout-land/dbinspector_activity_edit.xml +++ b/dbinspector/src/main/res/layout-land/dbinspector_activity_edit.xml @@ -7,7 +7,6 @@ @@ -83,7 +82,7 @@ diff --git a/dbinspector/src/main/res/layout/dbinspector_activity_databases.xml b/dbinspector/src/main/res/layout/dbinspector_activity_databases.xml index 038c84c6..1df515ce 100644 --- a/dbinspector/src/main/res/layout/dbinspector_activity_databases.xml +++ b/dbinspector/src/main/res/layout/dbinspector_activity_databases.xml @@ -7,7 +7,6 @@ @@ -56,7 +55,7 @@ @@ -82,7 +81,7 @@ @@ -44,7 +43,7 @@ android:orientation="vertical"> diff --git a/dbinspector/src/main/res/layout/dbinspector_activity_schema.xml b/dbinspector/src/main/res/layout/dbinspector_activity_schema.xml index e2862260..a33e54f8 100644 --- a/dbinspector/src/main/res/layout/dbinspector_activity_schema.xml +++ b/dbinspector/src/main/res/layout/dbinspector_activity_schema.xml @@ -8,7 +8,6 @@ diff --git a/dbinspector/src/main/res/layout/dbinspector_activity_settings.xml b/dbinspector/src/main/res/layout/dbinspector_activity_settings.xml index 72a16913..fe06e61e 100644 --- a/dbinspector/src/main/res/layout/dbinspector_activity_settings.xml +++ b/dbinspector/src/main/res/layout/dbinspector_activity_settings.xml @@ -7,7 +7,6 @@ @@ -49,7 +48,7 @@ android:orientation="vertical"> + #99CBFF + #00497A + #CFE5FF + #85CEFF + #004C6F + #C7E6FF #2a2a2a #353535 - @android:color/transparent #303030 #323232 - #d32f2f - #388e3c - #d50000 + #FFB4A9 + #00cc99 + #FFB4A9 \ No newline at end of file diff --git a/dbinspector/src/main/res/values/colors.xml b/dbinspector/src/main/res/values/colors.xml index 65f41c9e..da747988 100644 --- a/dbinspector/src/main/res/values/colors.xml +++ b/dbinspector/src/main/res/values/colors.xml @@ -1,12 +1,19 @@ #0f80cc - #003b57 + #cfe5ff + #001d36 + #006491 + #C7E6FF + #001E30 #bbbbbb #F2F2F2 - #003b57 #e1e1e1 #35000000 + #BA1B1B + #62C370 + #BA1B1B + #008ac0 #EB8258 #CC2E28 @@ -15,7 +22,4 @@ #BB3A8C #abc96a #aaaaaa - #f44336 - #4caf50 - #ff1744 \ No newline at end of file diff --git a/dbinspector/src/main/res/values/dimens.xml b/dbinspector/src/main/res/values/dimens.xml index f2ef9779..7704ace5 100644 --- a/dbinspector/src/main/res/values/dimens.xml +++ b/dbinspector/src/main/res/values/dimens.xml @@ -9,7 +9,7 @@ 10dp 40dp - 18dp + 4dp 16dp 8dp diff --git a/dbinspector/src/main/res/values/themes.xml b/dbinspector/src/main/res/values/themes.xml index cfc98ed4..d8765b67 100644 --- a/dbinspector/src/main/res/values/themes.xml +++ b/dbinspector/src/main/res/values/themes.xml @@ -1,26 +1,26 @@ - - - - diff --git a/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractorTest.kt b/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractorTest.kt index 89366800..d2324c13 100644 --- a/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractorTest.kt +++ b/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/CloseConnectionInteractorTest.kt @@ -17,13 +17,13 @@ internal class CloseConnectionInteractorTest : BaseTest() { override fun modules(): List = listOf( module { - factory { mockk() } + factory { mockk() } } ) @Test fun `Invoking interactor invokes source closeConnection`() { - val source: Sources.Memory = get() + val source: Sources.Memory.Connection = get() val interactor = CloseConnectionInteractor(source) coEvery { source.closeConnection(any()) } returns mockk() diff --git a/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractorTest.kt b/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractorTest.kt index 4793e3c3..4397dd7a 100644 --- a/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractorTest.kt +++ b/dbinspector/src/test/kotlin/com/infinum/dbinspector/domain/connection/interactors/OpenConnectionInteractorTest.kt @@ -17,13 +17,13 @@ internal class OpenConnectionInteractorTest : BaseTest() { override fun modules(): List = listOf( module { - factory { mockk() } + factory { mockk() } } ) @Test fun `Invoking interactor invokes source openConnection`() { - val source: Sources.Memory = get() + val source: Sources.Memory.Connection = get() val interactor = OpenConnectionInteractor(source) coEvery { source.openConnection(any()) } returns mockk() diff --git a/detekt.gradle b/detekt.gradle index 90907844..db526e49 100644 --- a/detekt.gradle +++ b/detekt.gradle @@ -3,7 +3,3 @@ apply plugin: "io.gitlab.arturbosch.detekt" detekt { toolVersion = libs.versions.detekt.get() } - -dependencies { - detektPlugins libs.detekt.formatting -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e41220b6..1ab1326b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,21 +1,5 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Settings specified in this file will override any Gradle settings -# configured through the IDE. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - android.useAndroidX=true -android.enableJetifier=false \ No newline at end of file +android.enableJetifier=false +android.disableAutomaticComponentCreation=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64aff174..f3a82ba0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,36 +1,35 @@ [versions] -dbinspector = "5.4.3" -gradle = "7.0.4" +dbinspector = "5.4.4" +gradle = "7.1.2" kotlin = "1.6.10" coroutines = "1.6.0" core = "1.7.0" -appcompat = "1.4.0" +appcompat = "1.4.1" activity = "1.4.0" -fragment = "1.4.0" -lifecycle = "2.4.0" +fragment = "1.4.1" +lifecycle = "2.4.1" viewpager = "1.0.0" -paging = "3.1.0" +paging = "3.1.1" recyclerview = "1.2.1" -startup = "1.1.0" +startup = "1.1.1" swiperefresh = "1.1.0" datastore = "1.0.0" dynamicanimation = "1.0.0" -design = "1.4.0" -protobuf-core = "3.19.1" +design = "1.5.0" +protobuf-core = "3.19.3" protobuf-plugin = "0.8.18" -koin = "3.1.4" -fuzzy = "1.3.1" -detekt = "1.18.0" -ktlintplugin = "10.2.0" -ktlint = "0.43.1" +koin = "3.1.5" +detekt = "1.20.0-RC1" +ktlintplugin = "10.2.1" +ktlint = "0.44.0" cpd = "3.2" -dokka = "1.6.0" +dokka = "1.6.10" kover = "0.4.4" jacoco = "0.8.7" intellij = "1.0.639" junit5 = "5.8.2" -mockk = "1.12.1" -mockito = "4.1.0" +mockk = "1.12.2" +mockito = "4.3.1" mockitokotlin = "4.0.0" turbine = "0.7.0" @@ -51,6 +50,7 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a androidx-activity = { module = "androidx.activity:activity-ktx", version.ref = "activity" } androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } androidx-lifecycle-viewmodel = {module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } androidx-viewpager = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } @@ -70,8 +70,6 @@ koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } koin-junit5 = { module = "io.insert-koin:koin-test-junit5", version.ref = "koin" } -fuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzy" } - detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } @@ -99,6 +97,7 @@ androidx = [ "androidx-activity", "androidx-fragment", "androidx-lifecycle-runtime", + "androidx-lifecycle-livedata", "androidx-lifecycle-viewmodel", "androidx-viewpager", "androidx-recyclerview", diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 111408b8..98839eb3 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -15,7 +15,7 @@ -