diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ce513878..71ddc46f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() - freeCompilerArgs = listOf("-Xallow-result-return-type", "-Xopt-in=kotlin.RequiresOptIn") + freeCompilerArgs = listOf("-Xallow-result-return-type", "-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.contracts.ExperimentalContracts") } defaultConfig { diff --git a/app/src/main/java/dev/patrickgold/florisboard/debug/Flog.kt b/app/src/main/java/dev/patrickgold/florisboard/debug/Flog.kt index c3c7cabf..32cc0085 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/debug/Flog.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/debug/Flog.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalContracts::class) - package dev.patrickgold.florisboard.debug import android.content.Context import android.os.Build import android.util.Log import java.lang.ref.WeakReference -import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/app/src/main/java/dev/patrickgold/florisboard/ime/keyboard/KeyboardIconSet.kt b/app/src/main/java/dev/patrickgold/florisboard/ime/keyboard/KeyboardIconSet.kt index ac8b72b0..4773dc40 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/ime/keyboard/KeyboardIconSet.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/ime/keyboard/KeyboardIconSet.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -@file:OptIn(ExperimentalContracts::class) - package dev.patrickgold.florisboard.ime.keyboard import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes -import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/app/src/main/java/dev/patrickgold/florisboard/ime/spelling/SpellingDict.kt b/app/src/main/java/dev/patrickgold/florisboard/ime/spelling/SpellingDict.kt index b5df3ee8..65e57746 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/ime/spelling/SpellingDict.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/ime/spelling/SpellingDict.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalContracts::class) - package dev.patrickgold.florisboard.ime.spelling import dev.patrickgold.florisboard.common.NativeInstanceWrapper @@ -26,7 +24,6 @@ import dev.patrickgold.florisboard.ime.nlp.Word import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import java.util.* -import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/app/src/main/java/dev/patrickgold/florisboard/ime/text/keyboard/TextKeyboardCache.kt b/app/src/main/java/dev/patrickgold/florisboard/ime/text/keyboard/TextKeyboardCache.kt index e4404397..6b03ce7d 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/ime/text/keyboard/TextKeyboardCache.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/ime/text/keyboard/TextKeyboardCache.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalContracts::class) - package dev.patrickgold.florisboard.ime.text.keyboard import androidx.collection.SparseArrayCompat @@ -24,7 +22,6 @@ import dev.patrickgold.florisboard.debug.* import dev.patrickgold.florisboard.ime.core.Subtype import kotlinx.coroutines.* import java.util.* -import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/app/src/main/java/dev/patrickgold/florisboard/res/AssetRef.kt b/app/src/main/java/dev/patrickgold/florisboard/res/AssetRef.kt index f13f69a4..76ca998e 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/res/AssetRef.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/res/AssetRef.kt @@ -24,6 +24,7 @@ package dev.patrickgold.florisboard.res * @property path The relative path to the asset within [source]. Must not begin and end with a * forward slash. */ +@Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using the new type and phase out this class.") data class AssetRef( val source: AssetSource, val path: String @@ -42,8 +43,20 @@ data class AssetRef( return Result.success(AssetRef(retSource, items[1])) } + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using FlorisUri.assets(path)", + ReplaceWith( + "FlorisRef.assets(path)", + "dev.patrickgold.florisboard.res.FlorisRef" + ) + ) fun assets(path: String) = AssetRef(AssetSource.Assets, path) + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using FlorisUri.internal(path)", + ReplaceWith( + "FlorisRef.internal(path)", + "dev.patrickgold.florisboard.res.FlorisRef" + ) + ) fun internal(path: String) = AssetRef(AssetSource.Internal, path) } diff --git a/app/src/main/java/dev/patrickgold/florisboard/res/AssetSource.kt b/app/src/main/java/dev/patrickgold/florisboard/res/AssetSource.kt index 2eed88d4..b52c1f3f 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/res/AssetSource.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/res/AssetSource.kt @@ -20,17 +20,20 @@ package dev.patrickgold.florisboard.res * Sealed class which specifies where an asset comes from. There are 3 different types, all of which * require a different approach on how to access the actual asset. */ +@Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using the new type and phase out this class.") sealed class AssetSource { /** * The asset comes pre-built with the application, thus all paths must be relative to the asset * directory of FlorisBoard. */ + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using FlorisUri.assets(path)") object Assets : AssetSource() /** * The asset is saved in the internal storage of FlorisBoard, all relative paths must therefore * be treated as such. */ + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using FlorisUri.internal(path)") object Internal : AssetSource() /** @@ -38,6 +41,7 @@ sealed class AssetSource { * data. Currently NYI. * TODO: Implement external extensions */ + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using the new type and phase out this class.") data class External(val packageName: String) : AssetSource() { override fun toString(): String { return super.toString() @@ -47,6 +51,7 @@ sealed class AssetSource { companion object { private val externalRegex: Regex = """^external\\(([a-z]+\\.)*[a-z]+\\)\$""".toRegex() + @Deprecated("This class will slowly be replaced by '.res.FlorisRef', as it unifies the Android URI type with the internal referencing system. Consider using the new type and phase out this class.") fun fromString(str: String): Result { return when (val string = str.lowercase()) { "assets" -> Result.success(Assets) diff --git a/app/src/main/java/dev/patrickgold/florisboard/res/ExternalContentUtils.kt b/app/src/main/java/dev/patrickgold/florisboard/res/ExternalContentUtils.kt index 1cd92575..d05a7ea3 100644 --- a/app/src/main/java/dev/patrickgold/florisboard/res/ExternalContentUtils.kt +++ b/app/src/main/java/dev/patrickgold/florisboard/res/ExternalContentUtils.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalContracts::class) - package dev.patrickgold.florisboard.res import android.content.Context @@ -24,7 +22,6 @@ import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.BufferedReader import java.io.BufferedWriter -import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/app/src/main/java/dev/patrickgold/florisboard/res/FlorisRef.kt b/app/src/main/java/dev/patrickgold/florisboard/res/FlorisRef.kt new file mode 100644 index 00000000..7f4a2f78 --- /dev/null +++ b/app/src/main/java/dev/patrickgold/florisboard/res/FlorisRef.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 Patrick Goldinger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.patrickgold.florisboard.res + +import android.net.Uri +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * A universal resource reference, capable to point to destinations within + * FlorisBoard's APK assets, cache and internal storage, external resources + * provided to FlorisBoard via content URIs, as well as hyperlinks. + * [android.net.Uri] is used as the underlying implementation for storing the + * reference and also handles parsing of raw string URIs. + * + * The reference is immutable. If a change is required, consider constructing + * a new reference with the provided builder methods. + * + * @property uri The underlying URI, which can be used for external references + * to pass along to the system. + */ +@JvmInline +value class FlorisRef private constructor(val uri: Uri) { + companion object { + private const val SCHEME_FLORIS_ASSETS = "floris-assets" + private const val SCHEME_FLORIS_CACHE = "floris-cache" + private const val SCHEME_FLORIS_INTERNAL = "floris-internal" + + /** + * Constructs a new [FlorisRef] pointing to a resource within the + * FlorisBoard APK assets. + * + * @param path The relative path from the APK assets root the resource + * is located. + * + * @return The newly constructed reference. + */ + fun assets(path: String) = Uri.Builder().run { + scheme(SCHEME_FLORIS_ASSETS) + path(path) + FlorisRef(build()) + } + + /** + * Constructs a new [FlorisRef] pointing to a resource within the + * cache storage of FlorisBoard. + * + * @param path The relative path from the cache root directory. + * + * @return The newly constructed reference. + */ + fun cache(path: String) = Uri.Builder().run { + scheme(SCHEME_FLORIS_CACHE) + path(path) + FlorisRef(build()) + } + + /** + * Constructs a new [FlorisRef] pointing to a resource within the + * internal storage of FlorisBoard. + * + * @param path The relative path from the internal storage root directory. + * + * @return The newly constructed reference. + */ + fun internal(path: String) = Uri.Builder().run { + scheme(SCHEME_FLORIS_INTERNAL) + path(path) + FlorisRef(build()) + } + + /** + * Constructs a new reference from given [uri], this can point to any + * destination, regardless of within FlorisBoard or not. + * + * @param uri The destination, denoted by a system URI format. + * + * @return The newly constructed reference. + */ + fun from(uri: Uri) = FlorisRef(uri) + + /** + * Constructs a new reference from given [str], this can point to any + * destination, regardless of within FlorisBoard or not. + * + * @param str An RFC 2396-compliant, encoded URI string. + * + * @return The newly constructed reference. + */ + fun from(str: String) = FlorisRef(Uri.parse(str)) + } + + /** + * True if the scheme indicates a reference to a FlorisBoard APK asset + * resource, false otherwise. + */ + val isAssets: Boolean + get() = uri.scheme == SCHEME_FLORIS_ASSETS + + /** + * True if the scheme indicates a reference to a FlorisBoard cache + * resource, false otherwise. + */ + val isCache: Boolean + get() = uri.scheme == SCHEME_FLORIS_CACHE + + /** + * True if the scheme indicates a reference to a FlorisBoard internal + * storage resource, false otherwise. + */ + val isInternal: Boolean + get() = uri.scheme == SCHEME_FLORIS_INTERNAL + + /** + * True if the scheme references any other external resource (URL, content + * resolver, etc.), false otherwise. + */ + val isExternal: Boolean + get() = !isAssets && !isCache && !isInternal + + /** + * Returns the scheme of this URI, or an empty string if no scheme is + * specified. + */ + val scheme: String + get() = uri.scheme ?: "" + + /** + * Returns the path of this URI, either relative or absolute depending on + * the use case of this reference, or an empty string if no path is + * specified. + */ + val path: String + get() = uri.path ?: "" + + /** + * Allows this URI to be used depending on where this reference points to. + * It is guaranteed that one of the four lambda parameters is executed. + * + * @param assets The lambda to run when the reference points to the FlorisBoard + * APK assets. Defaults to do nothing. + * @param cache The lambda to run when the reference points to the FlorisBoard + * cache resources. Defaults to do nothing. + * @param internal The lambda to run when the reference points to the FlorisBoard + * internal storage. Defaults to do nothing. + * @param external The lambda to run when the reference points to an external + * resource. Defaults to do nothing. + */ + fun whenSchemeIs( + assets: (ref: FlorisRef) -> Unit = { /* Do nothing */ }, + cache: (ref: FlorisRef) -> Unit = { /* Do nothing */ }, + internal: (ref: FlorisRef) -> Unit = { /* Do nothing */ }, + external: (ref: FlorisRef) -> Unit = { /* Do nothing */ } + ) { + contract { + callsInPlace(assets, InvocationKind.AT_MOST_ONCE) + callsInPlace(cache, InvocationKind.AT_MOST_ONCE) + callsInPlace(internal, InvocationKind.AT_MOST_ONCE) + callsInPlace(external, InvocationKind.AT_MOST_ONCE) + } + when { + isAssets -> assets(this) + isCache -> internal(this) + isInternal -> internal(this) + else -> external(this) + } + } + + /** + * Returns the encoded string representation of this URI. + */ + override fun toString(): String { + return uri.toString() + } +}