From 413a1ce79bb16a229b22970282a97c62ecadfb6a Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 23 May 2019 17:56:29 +0200 Subject: New module for Apache Cordova integration (experimental). --- kvision-modules/kvision-cordova/build.gradle | 5 ++ .../kotlin/pl/treksoft/kvision/cordova/Battery.kt | 54 +++++++++++++++++ .../kotlin/pl/treksoft/kvision/cordova/Device.kt | 69 ++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 kvision-modules/kvision-cordova/build.gradle create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/build.gradle b/kvision-modules/kvision-cordova/build.gradle new file mode 100644 index 00000000..4aaef76d --- /dev/null +++ b/kvision-modules/kvision-cordova/build.gradle @@ -0,0 +1,5 @@ +apply from: "../shared.gradle" + +dependencies { + compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion" +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt new file mode 100644 index 00000000..c8c26475 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Battery status event types. + */ +enum class BatteryEvent(internal var event: String) { + BATTERY_STATUS("batterystatus"), + BATTERY_LOW("batterylow"), + BATTERY_CRITICAL("batterycritical") +} + +/** + * Battery status. + */ +external class BatteryStatus { + val level: Int = definedExternally + val isPlugged: Boolean = definedExternally +} + +/** + * Add listeners for battery status Cordova events. + */ +fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) { + addDeviceReadyListener { + window.addEventListener(event.event, { status -> + @Suppress("UnsafeCastFromDynamic") + listener(status.asDynamic()) + }, false) + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt new file mode 100644 index 00000000..bf273ca7 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.document +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Device information class. + */ +external class Device { + val cordova: String = definedExternally + val model: String = definedExternally + val platform: String = definedExternally + val uuid: String = definedExternally + val version: String = definedExternally + val manufacturer: String = definedExternally + val isVirtual: Boolean = definedExternally + val serial: String = definedExternally +} + +/** + * External device information object. + */ +external val device: Device + +private var intDevice: Device? = null + +/** + * Add listeners for 'deviceready' Cordova event. + */ +fun addDeviceReadyListener(listener: (Device) -> Unit) { + document.addEventListener("deviceready", { + listener(device) + }, false) +} + +/** + * Suspending function to return device information object. + */ +suspend fun getDevice(): Device { + return intDevice ?: suspendCoroutine { continuation -> + addDeviceReadyListener { + intDevice = device + continuation.resume(device) + } + } +} -- cgit From d68eb4f4107576504887969eda53256b4d1c1e5c Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sat, 25 May 2019 13:49:14 +0200 Subject: Implement Cordova API: lifecycle events, camera plugin. --- .../kotlin/pl/treksoft/kvision/cordova/Battery.kt | 6 +- .../kotlin/pl/treksoft/kvision/cordova/Camera.kt | 269 +++++++++++++++++++++ .../kotlin/pl/treksoft/kvision/cordova/Device.kt | 52 +++- .../kotlin/pl/treksoft/kvision/cordova/Result.kt | 151 ++++++++++++ 4 files changed, 467 insertions(+), 11 deletions(-) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt index c8c26475..c6ac2a00 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -27,7 +27,7 @@ import kotlin.browser.window /** * Battery status event types. */ -enum class BatteryEvent(internal var event: String) { +enum class BatteryEvent(internal val event: String) { BATTERY_STATUS("batterystatus"), BATTERY_LOW("batterylow"), BATTERY_CRITICAL("batterycritical") @@ -37,8 +37,8 @@ enum class BatteryEvent(internal var event: String) { * Battery status. */ external class BatteryStatus { - val level: Int = definedExternally - val isPlugged: Boolean = definedExternally + val level: Int + val isPlugged: Boolean } /** diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt new file mode 100644 index 00000000..885bfd08 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Exception class for camera errors. + */ +class CameraException(message: String) : Exception(message) + +/** + * Main object for Cordova camera. + */ +object Camera { + + private const val CAMERA_ACTIVE_STORAGE_KEY = "kv_camera_active_storage_key" + private const val CAMERA_STATUS_OK = "OK" + + /** + * Camera destination types. + */ + enum class DestinationType { + DATA_URL, + FILE_URI, + NATIVE_URI + } + + /** + * Picture encoding types. + */ + enum class EncodingType { + JPEG, + PNG + } + + /** + * Picture/video media types. + */ + enum class MediaType { + PICTURE, + VIDEO, + ALLMEDIA + } + + /** + * Camera picture/video sources. + */ + enum class PictureSourceType { + PHOTOLIBRARY, + CAMERA, + SAVEDPHOTOALBUM + } + + /** + * Camera facing types. + */ + enum class Direction { + BACK, + FRONT + } + + /** + * iOS popover arrow directions. + */ + enum class PopoverArrowDirection { + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, + ARROW_ANY + } + + /** + * iOS popover options. + */ + data class CameraPopoverOptions( + val x: Int, + val y: Int, + val width: Int, + val height: Int, + val arrowDir: PopoverArrowDirection, + val popoverWidth: Int, + val popoverHeight: Int + ) + + /** + * Suspending function to get picture from the camera. + * + * Note: On Android platform you must also use [addCameraResultCallback] listener. + * + * @param options camera options + * @return a [Result] class containing the picture or the exception + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getPicture(options: CameraOptions): Result { + return suspendCoroutine { continuation -> + getPicture(options) { + continuation.resume(it) + } + } + } + + /** + * A function to get picture from the camera. + * + * Note: On Android platform you must also use [addCameraResultCallback] listener. + * + * @param options camera options + * @param resultCallback a callback function to get the [Result], containing the picture or the exception + */ + @Suppress("UnsafeCastFromDynamic") + fun getPicture(options: CameraOptions, resultCallback: (Result) -> Unit) { + window.localStorage.setItem(CAMERA_ACTIVE_STORAGE_KEY, "true") + addDeviceReadyListener { + window.navigator.asDynamic().camera.getPicture({ image -> + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + resultCallback(Result.success(image)) + }, { message -> + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + resultCallback(Result.error(CameraException(message))) + }, options.toJs()) + } + } + + /** + * An Android specific function to get picture from the camera after resume when the application + * webview intent is killed. + * + * @param resultCallback a callback function to get the [Result], containing the picture or the exception + */ + fun addCameraResultCallback(resultCallback: (Result) -> Unit) { + addResumeListener { resumeEvent -> + val isCameraActive = window.localStorage.getItem(CAMERA_ACTIVE_STORAGE_KEY) == "true" + if (isCameraActive && resumeEvent.pendingResult != null) { + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + if (resumeEvent.pendingResult.pluginStatus == CAMERA_STATUS_OK) { + resultCallback(Result.success(resumeEvent.pendingResult.result)) + } else { + resultCallback(Result.error(CameraException(resumeEvent.pendingResult.result))) + } + } + } + } + + /** + * Removes intermediate image files that are kept in the temporary storage after calling [getPicture]. + * + * @param resultCallback an optional callback function to get the [Result] of the cleanup operation. + */ + @Suppress("UnsafeCastFromDynamic") + fun cleanup(resultCallback: ((Result) -> Unit)? = null) { + addDeviceReadyListener { + window.navigator.asDynamic().camera.cleanup({ + resultCallback?.invoke(Result.success(CAMERA_STATUS_OK)) + }, { message -> + resultCallback?.invoke(Result.error(CameraException(message))) + }) + } + } +} + +internal fun Camera.DestinationType.toJs(): dynamic = when (this) { + Camera.DestinationType.DATA_URL -> js("Camera.DestinationType.DATA_URL") + Camera.DestinationType.FILE_URI -> js("Camera.DestinationType.FILE_URI") + Camera.DestinationType.NATIVE_URI -> js("Camera.DestinationType.NATIVE_URI") +} + +internal fun Camera.EncodingType.toJs(): dynamic = when (this) { + Camera.EncodingType.JPEG -> js("Camera.EncodingType.JPEG") + Camera.EncodingType.PNG -> js("Camera.EncodingType.PNG") +} + +internal fun Camera.MediaType.toJs(): dynamic = when (this) { + Camera.MediaType.PICTURE -> js("Camera.MediaType.PICTURE") + Camera.MediaType.VIDEO -> js("Camera.MediaType.VIDEO") + Camera.MediaType.ALLMEDIA -> js("Camera.MediaType.ALLMEDIA") +} + +internal fun Camera.PictureSourceType.toJs(): dynamic = when (this) { + Camera.PictureSourceType.PHOTOLIBRARY -> js("Camera.PictureSourceType.PHOTOLIBRARY") + Camera.PictureSourceType.CAMERA -> js("Camera.PictureSourceType.CAMERA") + Camera.PictureSourceType.SAVEDPHOTOALBUM -> js("Camera.PictureSourceType.SAVEDPHOTOALBUM") +} + +internal fun Camera.PopoverArrowDirection.toJs(): dynamic = when (this) { + Camera.PopoverArrowDirection.ARROW_UP -> js("Camera.PopoverArrowDirection.ARROW_UP") + Camera.PopoverArrowDirection.ARROW_DOWN -> js("Camera.PopoverArrowDirection.ARROW_DOWN") + Camera.PopoverArrowDirection.ARROW_LEFT -> js("Camera.PopoverArrowDirection.ARROW_LEFT") + Camera.PopoverArrowDirection.ARROW_RIGHT -> js("Camera.PopoverArrowDirection.ARROW_RIGHT") + Camera.PopoverArrowDirection.ARROW_ANY -> js("Camera.PopoverArrowDirection.ARROW_ANY") +} + +internal fun Camera.Direction.toJs(): dynamic = when (this) { + Camera.Direction.BACK -> js("Camera.Direction.BACK") + Camera.Direction.FRONT -> js("Camera.Direction.FRONT") +} + +internal external class CameraPopoverOptions( + x: Int, + y: Int, + width: Int, + height: Int, + arrowDir: dynamic, + popoverWidth: Int, + popoverHeight: Int +) + +internal fun Camera.CameraPopoverOptions.toJs(): dynamic { + return CameraPopoverOptions(x, y, width, height, arrowDir.toJs(), popoverWidth, popoverHeight) +} + +/** + * Camera options. + */ +data class CameraOptions( + val quality: Int? = null, + val destinationType: Camera.DestinationType? = null, + val sourceType: Camera.PictureSourceType? = null, + val allowEdit: Boolean? = null, + val encodingType: Camera.EncodingType? = null, + val targetWidth: Int? = null, + val targetHeight: Int? = null, + val mediaType: Camera.MediaType? = null, + val correctOrientation: Boolean? = null, + val saveToPhotoAlbum: Boolean? = null, + val popoverOptions: Camera.CameraPopoverOptions? = null, + val cameraDirection: Camera.Direction? = null +) + +internal fun CameraOptions.toJs(): dynamic { + return obj { + if (quality != null) this.quality = quality + if (destinationType != null) this.destinationType = destinationType.toJs() + if (sourceType != null) this.sourceType = sourceType.toJs() + if (allowEdit != null) this.allowEdit = allowEdit + if (encodingType != null) this.encodingType = encodingType.toJs() + if (targetWidth != null) this.targetWidth = targetWidth + if (targetHeight != null) this.targetHeight = targetHeight + if (mediaType != null) this.mediaType = mediaType.toJs() + if (correctOrientation != null) this.correctOrientation = correctOrientation + if (saveToPhotoAlbum != null) this.saveToPhotoAlbum = saveToPhotoAlbum + if (popoverOptions != null) this.popoverOptions = popoverOptions.toJs() + if (cameraDirection != null) this.cameraDirection = cameraDirection.toJs() + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt index bf273ca7..69e3defb 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt @@ -30,14 +30,31 @@ import kotlin.coroutines.suspendCoroutine * Device information class. */ external class Device { - val cordova: String = definedExternally - val model: String = definedExternally - val platform: String = definedExternally - val uuid: String = definedExternally - val version: String = definedExternally - val manufacturer: String = definedExternally - val isVirtual: Boolean = definedExternally - val serial: String = definedExternally + val cordova: String + val model: String + val platform: String + val uuid: String + val version: String + val manufacturer: String + val isVirtual: Boolean + val serial: String +} + +/** + * Pending result class. + */ +external class PendingResult { + val pluginServiceName: String + val pluginStatus: String + val result: dynamic +} + +/** + * Resume event class. + */ +external class ResumeEvent { + val action: String? + val pendingResult: PendingResult? } /** @@ -56,6 +73,25 @@ fun addDeviceReadyListener(listener: (Device) -> Unit) { }, false) } +/** + * Add listeners for 'pause' Cordova event. + */ +fun addPauseListener(listener: () -> Unit) { + document.addEventListener("pause", { + listener() + }, false) +} + +/** + * Add listeners for 'resume' Cordova event. + */ +fun addResumeListener(listener: (ResumeEvent) -> Unit) { + document.addEventListener("resume", { e -> + @Suppress("UnsafeCastFromDynamic") + listener(e.asDynamic()) + }, false) +} + /** * Suspending function to return device information object. */ diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt new file mode 100644 index 00000000..bb125e24 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt @@ -0,0 +1,151 @@ +/* + * Source: https://github.com/kittinunf/Result + * + * MIT License + * + * Copyright (c) 2017 Kittinun Vantasin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +inline fun Result<*, *>.getAs() = when (this) { + is Result.Success -> value as? X + is Result.Failure -> error as? X +} + +fun Result.success(f: (V) -> Unit) = fold(f, {}) + +fun Result<*, E>.failure(f: (E) -> Unit) = fold({}, f) + +infix fun Result.or(fallback: V) = when (this) { + is Result.Success -> this + else -> Result.Success(fallback) +} + +infix fun Result.getOrElse(fallback: V) = when (this) { + is Result.Success -> value + else -> fallback +} + +inline fun Result.map(transform: (V) -> U): Result = try { + when (this) { + is Result.Success -> Result.Success(transform(value)) + is Result.Failure -> Result.Failure(error) + } +} catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + Result.error(ex as E) +} + +inline fun Result.flatMap(transform: (V) -> Result): Result = try { + when (this) { + is Result.Success -> transform(value) + is Result.Failure -> Result.Failure(error) + } +} catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + Result.error(ex as E) +} + +fun Result.mapError(transform: (E) -> E2) = when (this) { + is Result.Success -> Result.Success(value) + is Result.Failure -> Result.Failure(transform(error)) +} + +fun Result.flatMapError(transform: (E) -> Result) = when (this) { + is Result.Success -> Result.Success(value) + is Result.Failure -> transform(error) +} + +fun Result.any(predicate: (V) -> Boolean): Boolean = try { + when (this) { + is Result.Success -> predicate(value) + is Result.Failure -> false + } +} catch (ex: Exception) { + false +} + +fun Result.fanout(other: () -> Result): Result, *> = + flatMap { outer -> other().map { outer to it } } + +sealed class Result { + + open operator fun component1(): V? = null + open operator fun component2(): E? = null + + inline fun fold(success: (V) -> X, failure: (E) -> X): X = when (this) { + is Success -> success(this.value) + is Failure -> failure(this.error) + } + + abstract fun get(): V + + class Success(val value: V) : Result() { + override fun component1(): V? = value + + override fun get(): V = value + + override fun toString() = "[Success: $value]" + + override fun hashCode(): Int = value.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return other is Success<*> && value == other.value + } + } + + class Failure(val error: E) : Result() { + override fun component2(): E? = error + + override fun get() = throw error + + fun getException(): E = error + + override fun toString() = "[Failure: $error]" + + override fun hashCode(): Int = error.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return other is Failure<*> && error == other.error + } + } + + companion object { + // Factory methods + fun error(ex: E) = Failure(ex) + + fun success(v: V) = Success(v) + + fun of(value: V?, fail: (() -> Exception) = { Exception() }): Result = + value?.let { success(it) } ?: error(fail()) + + fun of(f: () -> V): Result = try { + success(f()) + } catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + error(ex as E) + } + } + +} -- cgit From 4577751d14166c3e4ae14e34bae71f5ce7e76d6d Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sat, 25 May 2019 23:29:04 +0200 Subject: Support for Cordova core events. --- .../kotlin/pl/treksoft/kvision/cordova/Battery.kt | 4 +- .../kotlin/pl/treksoft/kvision/cordova/Device.kt | 60 +++++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt index c6ac2a00..5d9b8e93 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -27,7 +27,7 @@ import kotlin.browser.window /** * Battery status event types. */ -enum class BatteryEvent(internal val event: String) { +enum class BatteryEvent(internal val type: String) { BATTERY_STATUS("batterystatus"), BATTERY_LOW("batterylow"), BATTERY_CRITICAL("batterycritical") @@ -46,7 +46,7 @@ external class BatteryStatus { */ fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) { addDeviceReadyListener { - window.addEventListener(event.event, { status -> + window.addEventListener(event.type, { status -> @Suppress("UnsafeCastFromDynamic") listener(status.asDynamic()) }, false) diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt index 69e3defb..d83b79d4 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt @@ -22,6 +22,7 @@ package pl.treksoft.kvision.cordova +import org.w3c.dom.events.Event import kotlin.browser.document import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -58,17 +59,35 @@ external class ResumeEvent { } /** - * External device information object. + * Cordova event types. */ -external val device: Device +enum class CordovaEvent(internal val type: String) { + DEVICEREADY("deviceready"), + PAUSE("pause"), + RESUME("resume"), + BACKBUTTON("backbutton"), + MENUBUTTON("menubutton"), + SEARCHBUTTON("searchbutton"), + STARTCALLBUTTON("startcallbutton"), + ENDCALLBUTTON("endcallbutton"), + VOLUMEDOWNBUTTON("volumedownbutton"), + VOLUMEUPBUTTON("volumeupbutton"), + ACTIVATED("activated") +} + +private external val device: Device -private var intDevice: Device? = null +/** + * Cordova device information object. + */ +var cordovaDevice: Device? = null + private set /** * Add listeners for 'deviceready' Cordova event. */ fun addDeviceReadyListener(listener: (Device) -> Unit) { - document.addEventListener("deviceready", { + document.addEventListener(CordovaEvent.DEVICEREADY.type, { listener(device) }, false) } @@ -77,28 +96,43 @@ fun addDeviceReadyListener(listener: (Device) -> Unit) { * Add listeners for 'pause' Cordova event. */ fun addPauseListener(listener: () -> Unit) { - document.addEventListener("pause", { - listener() - }, false) + addDeviceReadyListener { + document.addEventListener(CordovaEvent.PAUSE.type, { + listener() + }, false) + } } /** * Add listeners for 'resume' Cordova event. */ fun addResumeListener(listener: (ResumeEvent) -> Unit) { - document.addEventListener("resume", { e -> - @Suppress("UnsafeCastFromDynamic") - listener(e.asDynamic()) - }, false) + addDeviceReadyListener { + document.addEventListener(CordovaEvent.RESUME.type, { e -> + @Suppress("UnsafeCastFromDynamic") + listener(e.asDynamic()) + }, false) + } +} + +/** + * Add listeners for a Cordova events. + */ +fun addCordovaEventListener(event: CordovaEvent, listener: (Event) -> Unit) { + addDeviceReadyListener { + document.addEventListener(event.type, { e -> + listener(e) + }, false) + } } /** * Suspending function to return device information object. */ suspend fun getDevice(): Device { - return intDevice ?: suspendCoroutine { continuation -> + return cordovaDevice ?: suspendCoroutine { continuation -> addDeviceReadyListener { - intDevice = device + cordovaDevice = device continuation.resume(device) } } -- cgit From ba0faac18b6641e90866d108f49bfc42148b4085 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sun, 26 May 2019 00:46:16 +0200 Subject: Support for Cordova notifications. --- .../pl/treksoft/kvision/cordova/Notification.kt | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt new file mode 100644 index 00000000..5afddb41 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * A response object for prompt function callback. + */ +external class PromptResponse { + val buttonIndex: Int + val input1: String +} + +/** + * Main object for Cordova notifications. + */ +object Notification { + + /** + * Show alert dialog. + * @param message a message + * @param title an optional dialog title (defaults to Alert) + * @param buttonLabel an optional button label (defaults to OK) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun alert(message: String, title: String? = null, buttonLabel: String? = null, callback: () -> Unit) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.alert(message, callback, title, buttonLabel) + } + } + + /** + * Show confirm dialog. + * @param message a message + * @param title an optional dialog title (defaults to Confirm) + * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel]) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun confirm(message: String, title: String? = null, buttonLabels: List? = null, callback: (Int) -> Unit) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.confirm(message, callback, title, buttonLabels?.toTypedArray()) + } + } + + /** + * Show prompt dialog. + * @param message a message + * @param title an optional dialog title (defaults to Prompt) + * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel]) + * @param defaultText default input value (default to empty string) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun prompt( + message: String, + title: String? = null, + buttonLabels: List? = null, + defaultText: String? = null, + callback: (PromptResponse) -> Unit + ) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.prompt( + message, + callback, + title, + buttonLabels?.toTypedArray(), + defaultText + ) + } + } + + /** + * Play a beep sound. + * @param times the number of times to repeat the beep (defaults to 1) + */ + @Suppress("UnsafeCastFromDynamic") + fun beep(times: Int = 1) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.beep(times) + } + } + +} -- cgit From 3101839b05347545a963c67df2b0d0506dc476d9 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 29 May 2019 12:53:47 +0200 Subject: Refactor battery api classes. --- .../kotlin/pl/treksoft/kvision/cordova/Battery.kt | 38 +++++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt index 5d9b8e93..84a7eb2e 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -24,15 +24,6 @@ package pl.treksoft.kvision.cordova import kotlin.browser.window -/** - * Battery status event types. - */ -enum class BatteryEvent(internal val type: String) { - BATTERY_STATUS("batterystatus"), - BATTERY_LOW("batterylow"), - BATTERY_CRITICAL("batterycritical") -} - /** * Battery status. */ @@ -42,13 +33,28 @@ external class BatteryStatus { } /** - * Add listeners for battery status Cordova events. + * Main object for Cordova battery. */ -fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) { - addDeviceReadyListener { - window.addEventListener(event.type, { status -> - @Suppress("UnsafeCastFromDynamic") - listener(status.asDynamic()) - }, false) +object Battery { + + /** + * Battery status event types. + */ + enum class BatteryEvent(internal val type: String) { + BATTERY_STATUS("batterystatus"), + BATTERY_LOW("batterylow"), + BATTERY_CRITICAL("batterycritical") + } + + /** + * Add listeners for battery status Cordova events. + */ + fun addStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) { + addDeviceReadyListener { + window.addEventListener(event.type, { status -> + @Suppress("UnsafeCastFromDynamic") + listener(status.asDynamic()) + }, false) + } } } -- cgit From cb495fb29c997f76a420a9e653f4fa087b8b2cf8 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 29 May 2019 12:53:56 +0200 Subject: Support for Cordova network api. --- .../kotlin/pl/treksoft/kvision/cordova/Network.kt | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt new file mode 100644 index 00000000..eb98f8a4 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.document +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Main object for Cordova network. + */ +object Network { + + /** + * Connection types + */ + enum class ConnectionType { + UNKNOWN, + ETHERNET, + WIFI, + CELL_2G, + CELL_3G, + CELL_4G, + CELL, + NONE + } + + /** + * Network status events + */ + enum class NetworkEvent(internal val type: String) { + OFFLINE("offline"), + ONLINE("online") + } + + /** + * Get network connection type. + */ + suspend fun getConnectionType(): ConnectionType { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val connectionType = when (window.navigator.asDynamic().connection.type) { + js("Connection.ETHERNET") -> ConnectionType.ETHERNET + js("Connection.WIFI") -> ConnectionType.WIFI + js("Connection.CELL_2G") -> ConnectionType.CELL_2G + js("Connection.CELL_3G") -> ConnectionType.CELL_3G + js("Connection.CELL_4G") -> ConnectionType.CELL_4G + js("Connection.CELL") -> ConnectionType.CELL + js("Connection.NONE") -> ConnectionType.NONE + else -> ConnectionType.UNKNOWN + } + continuation.resume(connectionType) + } + } + } + + /** + * Add listeners for network status Cordova events. + */ + fun addStatusListener(event: NetworkEvent, listener: () -> Unit) { + addDeviceReadyListener { + document.addEventListener(event.type, { + listener() + }, false) + } + } + +} -- cgit From 27447d5c0bb61a00511cbb03180e141b367e708d Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 29 May 2019 14:44:35 +0200 Subject: Support for Cordova screen orientation api. --- .../kotlin/pl/treksoft/kvision/cordova/Screen.kt | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt new file mode 100644 index 00000000..6765b44e --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova screen. + */ +object Screen { + + /** + * Screen orientations + */ + enum class Orientation(internal val type: String) { + PORTRAIT_PRIMARY("portrait-primary"), + PORTRAIT_SECONDARY("portrait-secondary"), + LANDSCAPE_PRIMARY("landscape-primary"), + LANDSCAPE_SECONDARY("landscape-secondary"), + PORTRAIT("portrait"), + LANDSCAPE("landscape"), + ANY("any") + } + + /** + * Lock screen orientation. + * @param orientation selected orientation + */ + fun lock(orientation: Orientation) { + window.screen.asDynamic().orientation.lock(orientation.type) + } + + /** + * Unlock screen orientation. + */ + fun unlock() { + window.screen.asDynamic().orientation.unlock() + } + + /** + * Get screen orientation. + */ + fun getOrientation(): Orientation { + val type = window.screen.asDynamic().orientation.type + return Orientation.values().find { it.type == type } ?: Screen.Orientation.ANY + } + + /** + * Add listeners for screen orientation change Cordova events. + */ + fun addOrientationChangeListener(listener: (Orientation) -> Unit) { + window.addEventListener("orientationchange", { + listener(getOrientation()) + }, false) + } +} -- cgit From 1490d855f926b5ae0c9b41d7ef904754e17d0204 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 29 May 2019 14:57:28 +0200 Subject: Support for Cordova splashscreen api. --- .../pl/treksoft/kvision/cordova/Splashscreen.kt | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt new file mode 100644 index 00000000..03157534 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova splashscreen. + */ +object Splashscreen { + + /** + * Show splashscreen. + */ + fun show() { + addDeviceReadyListener { + window.navigator.asDynamic().splashscreen.show() + } + } + + /** + * Hide splashscreen. + */ + fun hide() { + addDeviceReadyListener { + window.navigator.asDynamic().splashscreen.hide() + } + } + +} -- cgit From f969c5ccf7f1187dedda6c46fec5b0047bc28ae5 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 29 May 2019 16:05:12 +0200 Subject: Support for Cordova status bar api. --- .../pl/treksoft/kvision/cordova/StatusBar.kt | 152 +++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt new file mode 100644 index 00000000..5617bf9b --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.toHexString +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@JsName("StatusBar") +internal external val StatusBarJs: dynamic + +/** + * Main object for Cordova status bar. + */ +object StatusBar { + + /** + * Show status bar. + */ + fun show() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.show() + } + } + + /** + * Hide status bar. + */ + fun hide() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.hide() + } + } + + /** + * Set overlay. + * @param overlays determines if the status bar overlays the web view + */ + fun overlaysWebView(overlays: Boolean) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.overlaysWebView(overlays) + } + } + + /** + * Use the default status bar (dark text, for light backgrounds). + */ + fun styleDefault() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleDefault() + } + } + + /** + * Use the lightContent status bar (light text, for dark backgrounds). + */ + fun styleLightContent() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleLightContent() + } + } + + /** + * Use the blackTranslucent status bar (light text, for dark backgrounds). + */ + fun styleBlackTranslucent() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleBlackTranslucent() + } + } + + /** + * Use the blackOpaque status bar (light text, for dark backgrounds). + */ + fun styleBlackOpaque() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleBlackOpaque() + } + } + + /** + * Sets the background color of the status bar by a hexadecimal Int. + */ + fun setBackgroundColor(color: Int) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.backgroundColorByHexString("#" + color.toHexString()) + } + } + + /** + * Sets the background color of the status bar by a hex string. + */ + fun setBackgroundColorByHexString(color: String) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.backgroundColorByHexString(color) + } + } + + /** + * Returns if the status bar is visible. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun isVisible(): Boolean { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + continuation.resume(StatusBarJs.isVisible) + } + } + } + + /** + * Add listeners for the status bar tap event. + */ + fun addTapListener(listener: () -> Unit) { + addDeviceReadyListener { + window.addEventListener("statusTap", { + listener() + }) + } + } + +} -- cgit From fc7f3588472423ce915113c8d2089e4df3793de0 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 30 May 2019 12:45:53 +0200 Subject: Support for Cordova vibration api. --- .../pl/treksoft/kvision/cordova/Vibration.kt | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt new file mode 100644 index 00000000..6d973bbc --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova vibration. + */ +object Vibration { + + /** + * Generate a vibration with a given pattern. + * @param pattern a pattern - sequence of durations (in milliseconds) for which to turn on or off the vibrator. + */ + fun vibrate(vararg pattern: Int) { + val patternArray = if (pattern.isEmpty()) arrayOf(0) else pattern.toTypedArray() + addDeviceReadyListener { + window.navigator.vibrate(patternArray) + } + } + +} -- cgit From d40abae23a4700447b000c55a177bfdb703fef44 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Fri, 7 Jun 2019 01:42:47 +0200 Subject: Support for Cordova file api. --- .../kotlin/pl/treksoft/kvision/cordova/File.kt | 542 +++++++++++++++++++++ .../pl/treksoft/kvision/cordova/FileSystem.kt | 221 +++++++++ .../pl/treksoft/kvision/cordova/FileWriter.kt | 47 ++ .../kotlin/pl/treksoft/kvision/cordova/Result.kt | 7 +- 4 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt new file mode 100644 index 00000000..df176e84 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt @@ -0,0 +1,542 @@ +@file:Suppress( + "TooManyFunctions", "TooGenericExceptionCaught" +) + +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import org.khronos.webgl.ArrayBuffer +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.set +import org.w3c.files.Blob +import org.w3c.files.BlobPropertyBag +import org.w3c.files.FileReader +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Exception class for file errors. + */ +class FileException(code: Int) : Exception("Error: $code") + +/** + * System directories class. + */ +external class SystemDirs { + val applicationDirectory: String + val applicationStorageDirectory: String + val dataDirectory: String + val cacheDirectory: String + val externalApplicationStorageDirectory: String? + val externalDataDirectory: String? + val externalCacheDirectory: String? + val externalRootDirectory: String? + val tempDirectory: String? + val syncedDataDirectory: String? + val documentsDirectory: String? + val sharedDirectory: String? +} + +/** + * Main object for Cordova file. + */ +object File { + + const val NOT_FOUND_ERR = 1 + const val SECURITY_ERR = 2 + const val ABORT_ERR = 3 + const val NOT_READABLE_ERR = 4 + const val ENCODING_ERR = 5 + const val NO_MODIFICATION_ALLOWED_ERR = 6 + const val INVALID_STATE_ERR = 7 + const val SYNTAX_ERR = 8 + const val INVALID_MODIFICATION_ERR = 9 + const val QUOTA_EXCEEDED_ERR = 10 + const val TYPE_MISMATCH_ERR = 11 + const val PATH_EXISTS_ERR = 12 + + /** + * File system types. + */ + enum class FileSystemType { + TEMPORARY, + PERSISTENT + } + + /** + * Get system directories. + */ + suspend fun getSystemDirectories(): SystemDirs { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + continuation.resume(window.asDynamic().cordova.file) + } + } + } + + /** + * Resolve given path to a file or directory entry. + * @param url directory path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURL(url: String): Result { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.asDynamic().resolveLocalFileSystemURL(url, { entry -> + continuation.resume(Result.Success(entry)) + }, { error -> + continuation.resume(Result.error(FileException(error.code))) + }) + } + } + } + + /** + * Resolve given path to a directory entry. + * @param url directory path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURLForDir(url: String): Result { + return when (val result = resolveLocalFileSystemURL(url)) { + is Result.Success -> { + if (result.value.isDirectory) { + result.map { + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + it as DirectoryEntry + } + } else { + Result.error(FileException(TYPE_MISMATCH_ERR)) + } + } + is Result.Failure -> result + } + } + + /** + * Resolve given path to a file entry. + * @param url file path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURLForFile(url: String): Result { + return when (val result = resolveLocalFileSystemURL(url)) { + is Result.Success -> { + if (result.value.isFile) { + result.map { + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + it as FileEntry + } + } else { + Result.error(FileException(TYPE_MISMATCH_ERR)) + } + } + is Result.Failure -> result + } + } + + /** + * Request a file system of a given type. + * @param fileSystemType file system type (temporary or persistent) + * @param size file system size (quota) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun requestFileSystem( + fileSystemType: File.FileSystemType, + size: Long = 0 + ): Result { + return suspendCoroutine { continuation -> + val type = when (fileSystemType) { + File.FileSystemType.TEMPORARY -> LocalFileSystem.TEMPORARY + File.FileSystemType.PERSISTENT -> LocalFileSystem.PERSISTENT + } + addDeviceReadyListener { + window.asDynamic().requestFileSystem(type, size, { fs -> + continuation.resume(Result.Success(fs)) + }, { error -> + continuation.resume(Result.error(FileException(error.code))) + }) + } + } + } + + internal fun dataURLtoBlob(dataURI: String): Blob { + val byteString = window.atob(dataURI.split(',')[1]) + val mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + val ab = ArrayBuffer(byteString.length) + val ia = Uint8Array(ab) + for (i in 0 until byteString.length) { + ia[i] = byteString[i].toByte() + } + return Blob(arrayOf(ab), BlobPropertyBag(type = mimeString)) + } + +} + +/** + * Extension function to convert String to a directory entry. + */ +suspend fun String.toDirectoryEntry(): Result { + return File.resolveLocalFileSystemURLForDir(this) +} + +/** + * Extension function to convert String to a file entry. + */ +suspend fun String.toFileEntry(): Result { + return File.resolveLocalFileSystemURLForFile(this) +} + +/** + * Get file or directory metadata. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.getMetadata(): Result { + return suspendCoroutine { continuation -> + this.getMetadata({ metadata: Metadata -> + continuation.resume(Result.success(metadata)) + } as MetadataCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get file or directory parent directory entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.getParent(): Result { + return suspendCoroutine { continuation -> + this.getParent({ directoryEntry: DirectoryEntry -> + continuation.resume(Result.success(directoryEntry)) + } as DirectoryEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Remove given file or directory. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.remove(): Result { + return suspendCoroutine { continuation -> + this.remove({ + continuation.resume(Result.success(this)) + } as VoidCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Move given file or directory to a new location. + * @param parent new location parent directory entry + * @param newName new location name + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.moveTo(parent: DirectoryEntry, newName: String? = null): Result { + return suspendCoroutine { continuation -> + this.moveTo(parent, newName, { entry: Entry -> + continuation.resume(Result.success(entry)) + } as EntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Copy given file or directory to a new location. + * @param parent new location parent directory entry + * @param newName new location name + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.copyTo(parent: DirectoryEntry, newName: String? = null): Result { + return suspendCoroutine { continuation -> + this.copyTo(parent, newName, { entry: Entry -> + continuation.resume(Result.success(entry)) + } as EntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Create a FileWriter object for a given file entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun FileEntry.createWriter(): Result { + return suspendCoroutine { continuation -> + this.createWriter({ fileWriter: FileWriter -> + continuation.resume(Result.success(fileWriter)) + } as FileWriterCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get a File object for a given file entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun FileEntry.file(): Result { + return suspendCoroutine { continuation -> + this.file({ file: org.w3c.files.File -> + continuation.resume(Result.success(file)) + } as FileCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get or create a file in a given parent directory. + * @param path target file path + * @param create Used to indicate that the user wants to create a file if it was not previously there. + * @param exclusive Fail if the target path file already exists. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.getFile( + path: String, + create: Boolean = true, + exclusive: Boolean = false +): Result { + return suspendCoroutine { continuation -> + this.getFile(path, obj { + this.create = create + this.exclusive = exclusive + }, { fileEntry: FileEntry -> + continuation.resume(Result.success(fileEntry)) + } as FileEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get or create a directory in a given parent directory. + * @param path target directory path + * @param create Used to indicate that the user wants to create a directory if it was not previously there. + * @param exclusive Fail if the target path directory already exists. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.getDirectory( + path: String, + create: Boolean = true, + exclusive: Boolean = false +): Result { + return suspendCoroutine { continuation -> + this.getDirectory(path, obj { + this.create = create + this.exclusive = exclusive + }, { directoryEntry: DirectoryEntry -> + continuation.resume(Result.success(directoryEntry)) + } as DirectoryEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Remove given directory recursively. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.removeRecursively(): Result { + return suspendCoroutine { continuation -> + this.removeRecursively({ + continuation.resume(Result.success(this)) + } as VoidCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * List directory entries for a given DirectoryReader. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryReader.readEntries(): Result, FileException> { + return suspendCoroutine { continuation -> + this.readEntries({ entries: Array -> + continuation.resume(Result.success(entries.toList())) + } as EntriesCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * List directory entries for a given parent directory entry. + */ +suspend fun DirectoryEntry.readEntries(): Result, FileException> { + return this.createReader().readEntries() +} + +/** + * Read file content as a plain string. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsText(): Result { + return this.file().flatMap { file -> + suspendCoroutine> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.readAsText(file) + } + } +} + +/** + * Read file content as a data url. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsDataURL(): Result { + return this.file().flatMap { file -> + suspendCoroutine> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.readAsDataURL(file) + } + } +} + +/** + * Read file content as an array buffer. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsArrayBuffer(): Result { + return this.file().flatMap { file -> + suspendCoroutine> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + reader.readAsArrayBuffer(file) + } + } +} + +/** + * Write file content from a Blob. + * @param data a data Blob to be written. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.write(data: Blob): Result { + return this.createWriter().flatMap { writer -> + suspendCoroutine> { continuation -> + writer.onwriteend = { + continuation.resume(Result.success(this)) + } + writer.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + writer.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + writer.write(data) + } + } +} + +/** + * Write file content from a plain string. + * @param data a data string to be written. + */ +suspend fun FileEntry.write(data: String): Result { + return this.write(Blob(arrayOf(data))) +} + +/** + * Write file content from a data url. + * @param dataUrl a data url to be written. + */ +suspend fun FileEntry.writeDataUrL(dataUrl: String): Result { + return this.write(File.dataURLtoBlob(dataUrl)) +} + +/** + * Write file content from an array buffer. + * @param data an array buffer to be written. + */ +suspend fun FileEntry.write(data: ArrayBuffer): Result { + return this.write(data.toBlob()) +} + +/** + * Append file content from a Blob. + * @param data a data Blob to be appended. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.append(data: Blob): Result { + return this.createWriter().flatMap { writer -> + try { + writer.seek(writer.length) + } catch (e: Exception) { + console.log("File doesn't exist!") + } + suspendCoroutine> { continuation -> + writer.onwriteend = { + continuation.resume(Result.success(this)) + } + writer.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + writer.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + writer.write(data) + } + } +} + +/** + * Append file content from a plain string. + * @param data a string to be appended. + */ +suspend fun FileEntry.append(data: String): Result { + return this.append(Blob(arrayOf(data))) +} + +/** + * Convert array buffer to a blob. + */ +fun ArrayBuffer.toBlob(): Blob { + return Blob(arrayOf(Uint8Array(this))) +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt new file mode 100644 index 00000000..48f4f3d2 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt @@ -0,0 +1,221 @@ +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", + "OVERRIDING_FINAL_MEMBER", + "RETURN_TYPE_MISMATCH_ON_OVERRIDE", + "CONFLICTING_OVERLOADS", + "EXTERNAL_DELEGATION", + "NESTED_CLASS_IN_EXTERNAL_INTERFACE", + "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength" +) + +package pl.treksoft.kvision.cordova + +import org.w3c.files.File +import kotlin.js.Date + +external object LocalFileSystem { + var TEMPORARY: Number + var PERSISTENT: Number + fun requestFileSystem( + type: Number, + size: Number, + successCallback: FileSystemCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun resolveLocalFileSystemURL( + url: String, + successCallback: EntryCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun webkitRequestFileSystem( + type: Number, + size: Number, + successCallback: FileSystemCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) +} + +external object LocalFileSystemSync { + var TEMPORARY: Number + var PERSISTENT: Number + fun requestFileSystemSync(type: Number, size: Number): FileSystemSync + fun resolveLocalFileSystemSyncURL(url: String): EntrySync + fun webkitRequestFileSystemSync(type: Number, size: Number): FileSystemSync +} + +external interface Metadata { + var modificationTime: Date + var size: Number +} + +external interface Flags { + var create: Boolean? get() = definedExternally; set(value) = definedExternally + var exclusive: Boolean? get() = definedExternally; set(value) = definedExternally +} + +external interface FileSystem { + var name: String + var root: DirectoryEntry +} + +external interface Entry { + var isFile: Boolean + var isDirectory: Boolean + fun getMetadata(successCallback: MetadataCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + var name: String + var fullPath: String + var filesystem: FileSystem + fun moveTo( + parent: DirectoryEntry, + newName: String? = definedExternally /* null */, + successCallback: EntryCallback? = definedExternally /* null */, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun copyTo( + parent: DirectoryEntry, + newName: String? = definedExternally /* null */, + successCallback: EntryCallback? = definedExternally /* null */, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun toURL(): String + fun toInternalURL(): String + fun remove(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + fun getParent(successCallback: DirectoryEntryCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface DirectoryEntry : Entry { + fun createReader(): DirectoryReader + fun getFile( + path: String, + options: Flags? = definedExternally /* null */, + successCallback: FileEntryCallback?, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun getDirectory( + path: String, + options: Flags? = definedExternally /* null */, + successCallback: DirectoryEntryCallback?, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun removeRecursively(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface DirectoryReader { + fun readEntries(successCallback: EntriesCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface FileEntry : Entry { + fun createWriter(successCallback: FileWriterCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + fun file(successCallback: FileCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface FileSystemCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileSystemCallback.invoke(filesystem: FileSystem) { + asDynamic()(filesystem) +} + +external interface EntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun EntryCallback.invoke(entry: Entry) { + asDynamic()(entry) +} + +external interface FileEntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileEntryCallback.invoke(entry: FileEntry) { + asDynamic()(entry) +} + +external interface DirectoryEntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun DirectoryEntryCallback.invoke(entry: DirectoryEntry) { + asDynamic()(entry) +} + +external interface EntriesCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun EntriesCallback.invoke(entries: Array) { + asDynamic()(entries) +} + +external interface MetadataCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun MetadataCallback.invoke(metadata: Metadata) { + asDynamic()(metadata) +} + +external interface FileWriterCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileWriterCallback.invoke(fileWriter: FileWriter) { + asDynamic()(fileWriter) +} + +external interface FileCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileCallback.invoke(file: File) { + asDynamic()(file) +} + +external interface VoidCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun VoidCallback.invoke() { + asDynamic()() +} + +external interface ErrorCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun ErrorCallback.invoke(err: Error) { + asDynamic()(err) +} + +external interface FileSystemSync { + var name: String + var root: DirectoryEntrySync +} + +external interface EntrySync { + var isFile: Boolean + var isDirectory: Boolean + fun getMetadata(): Metadata + var name: String + var fullPath: String + var filesystem: FileSystemSync + fun moveTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync + fun copyTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync + fun toURL(): String + fun remove() + fun getParent(): DirectoryEntrySync +} + +external interface DirectoryEntrySync : EntrySync { + fun createReader(): DirectoryReaderSync + fun getFile(path: String, options: Flags? = definedExternally /* null */): FileEntrySync + fun getDirectory(path: String, options: Flags? = definedExternally /* null */): DirectoryEntrySync + fun removeRecursively() +} + +external interface DirectoryReaderSync { + fun readEntries(): Array +} + +external interface FileEntrySync : EntrySync { + fun createWriter(): FileWriterSync + fun file(): File +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt new file mode 100644 index 00000000..26215aa8 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt @@ -0,0 +1,47 @@ +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", + "OVERRIDING_FINAL_MEMBER", + "RETURN_TYPE_MISMATCH_ON_OVERRIDE", + "CONFLICTING_OVERLOADS", + "EXTERNAL_DELEGATION", + "NESTED_CLASS_IN_EXTERNAL_INTERFACE", + "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength" +) + +package pl.treksoft.kvision.cordova + +import org.w3c.dom.events.EventTarget +import org.w3c.files.Blob +import org.w3c.xhr.ProgressEvent + +external interface FileSaver : EventTarget { + fun abort() + var INIT: Number + var WRITING: Number + var DONE: Number + var readyState: Number + var error: Error + var onwritestart: (event: ProgressEvent) -> Unit + var onprogress: (event: ProgressEvent) -> Unit + var onwrite: (event: ProgressEvent) -> Unit + var onabort: (event: ProgressEvent) -> Unit + var onerror: (event: ProgressEvent) -> Unit + var onwriteend: (event: ProgressEvent) -> Unit + +} + +external interface FileWriter : FileSaver { + var position: Number + var length: Number + fun write(data: Blob) + fun seek(offset: Number) + fun truncate(size: Number) +} + +external interface FileWriterSync { + var position: Number + var length: Number + fun write(data: Blob) + fun seek(offset: Number) + fun truncate(size: Number) +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt index bb125e24..83a1987e 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt @@ -1,3 +1,6 @@ +@file:Suppress( + "TooManyFunctions", "TooGenericExceptionCaught" +) /* * Source: https://github.com/kittinunf/Result * @@ -31,9 +34,9 @@ inline fun Result<*, *>.getAs() = when (this) { is Result.Failure -> error as? X } -fun Result.success(f: (V) -> Unit) = fold(f, {}) +inline fun Result.success(f: (V) -> Unit) = fold(f, {}) -fun Result<*, E>.failure(f: (E) -> Unit) = fold({}, f) +inline fun Result<*, E>.failure(f: (E) -> Unit) = fold({}, f) infix fun Result.or(fallback: V) = when (this) { is Result.Success -> this -- cgit From c73905e037608c76b08e4a3327fc53fe72a96622 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Fri, 7 Jun 2019 01:43:13 +0200 Subject: Code style fixes. --- .../src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt | 1 + .../src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt | 2 ++ .../src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt | 6 ++++-- 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt index 885bfd08..40c731c7 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt @@ -251,6 +251,7 @@ data class CameraOptions( val cameraDirection: Camera.Direction? = null ) +@Suppress("ComplexMethod") internal fun CameraOptions.toJs(): dynamic { return obj { if (quality != null) this.quality = quality diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt index 5617bf9b..e3de6af2 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt @@ -27,12 +27,14 @@ import kotlin.browser.window import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +@Suppress("TopLevelPropertyNaming") @JsName("StatusBar") internal external val StatusBarJs: dynamic /** * Main object for Cordova status bar. */ +@Suppress("TooManyFunctions") object StatusBar { /** diff --git a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt index 9eb9d9a7..39e7c2dc 100644 --- a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt +++ b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt @@ -23,7 +23,7 @@ package pl.treksoft.kvision.moment import kotlin.js.Date -@Suppress("unused") +@Suppress("unused", "TooManyFunctions") @JsNonModule @JsModule("moment") open external class Moment { @@ -79,7 +79,9 @@ open external class Moment { open fun isSameOrAfter(moment: dynamic): Boolean = definedExternally open fun isSameOrAfter(moment: dynamic, key: String): Boolean = definedExternally open fun isBetween(firstMoment: dynamic, secondMoment: dynamic): Boolean = definedExternally - open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = definedExternally + open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = + definedExternally + open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String): Boolean = definedExternally open fun isDST(): Boolean = definedExternally open fun isDSTShifted(): Boolean = definedExternally -- cgit From 5f1681474cdfb7a140adc8e93414e488b335d29e Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Fri, 7 Jun 2019 20:03:50 +0200 Subject: New getStatus() function for cordova battery api. --- .../src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt index 84a7eb2e..656a519a 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -23,6 +23,8 @@ package pl.treksoft.kvision.cordova import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine /** * Battery status. @@ -57,4 +59,15 @@ object Battery { }, false) } } + + /** + * Get battery status. + */ + suspend fun getStatus(): BatteryStatus { + return suspendCoroutine { continuation -> + addStatusListener(Battery.BatteryEvent.BATTERY_STATUS) { status -> + continuation.resume(status) + } + } + } } -- cgit From 7b9fb94d618fcf619de410cdeb9c000f21991b71 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sat, 8 Jun 2019 00:18:11 +0200 Subject: Support for Cordova geolocation api. --- .../pl/treksoft/kvision/cordova/Geolocation.kt | 263 +++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt new file mode 100644 index 00000000..9f4d9cfe --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Geolocation coordinates values. + */ +external class Coordinates { + val latitude: Number + val longitude: Number + val altitude: Number + val accuracy: Number + val altitudeAccuracy: Number? + val heading: Number? + val speed: Number +} + +/** + * Geolocation position value. + */ +external class Position { + val coords: Coordinates + val timestamp: Number +} + +/** + * Geolocaton error codes. + */ +enum class PositionError { + PERMISSION_DENIED, + POSITION_UNAVAILABLE, + TIMEOUT +} + +private fun codeToEnum(code: Int): PositionError { + return when (code) { + 1 -> PositionError.PERMISSION_DENIED + 2 -> PositionError.POSITION_UNAVAILABLE + else -> PositionError.TIMEOUT + } +} + +/** + * Exception class for geolocation errors. + */ +class GeolocationException(val code: PositionError, message: String) : Exception(message) + +/** + * Main geolocation object based on webview api. + */ +object Geolocation { + + /** + * Get current location. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getCurrentPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null + ): Result { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position -> + continuation.resume(Result.Success(position)) + }, { error -> + continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + }) + } + } + } + + /** + * Watch location changes. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param resultCallback a callback function receiving the result + * @return watch identifier (can be removed with a [clearWatch] function) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun watchPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + resultCallback: (Result) -> Unit + ): String? { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position -> + resultCallback(Result.Success(position)) + }, { error -> + resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + }) + continuation.resume(watchId) + } + } + } + + /** + * Clear the given watch. + * @param watchId watch identifier returned from [watchPosition] function + */ + @Suppress("UnsafeCastFromDynamic") + fun clearWatch(watchId: String) { + if (window.navigator.asDynamic().geolocation != null) { + window.navigator.asDynamic().geolocation.clearWatch(watchId) + } + } + +} + +/** + * Main geolocation object based on Google location services api. + */ +object Locationservices { + + /** + * Location services priority. + */ + enum class Priority { + PRIORITY_HIGH_ACCURACY, + PRIORITY_BALANCED_POWER_ACCURACY, + PRIORITY_LOW_POWER, + PRIORITY_NO_POWER + } + + /** + * Get current location. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param priority the priority of the request is a strong hint for which location sources to use + * @param interval set the desired interval for active location updates, in milliseconds + * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getCurrentPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + priority: Priority? = null, + interval: Number? = null, + fastInterval: Number? = null + ): Result { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.asDynamic() + .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position -> + continuation.resume(Result.Success(position)) + }, { error -> + continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + if (priority != null) this.priority = getJsPriority(priority) + if (interval != null) this.interval = interval + if (fastInterval != null) this.fastInterval = fastInterval + }) + } + } + } + + /** + * Watch location changes. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param priority the priority of the request is a strong hint for which location sources to use + * @param interval set the desired interval for active location updates, in milliseconds + * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds + * @param resultCallback a callback function receiving the result + * @return watch identifier (can be removed with a [clearWatch] function) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun watchPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + priority: Priority? = null, + interval: Number? = null, + fastInterval: Number? = null, + resultCallback: (Result) -> Unit + ): String? { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val watchId = + window.asDynamic() + .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position -> + resultCallback(Result.Success(position)) + }, { error -> + resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + if (priority != null) this.priority = getJsPriority(priority) + if (interval != null) this.interval = interval + if (fastInterval != null) this.fastInterval = fastInterval + }) + continuation.resume(watchId) + } + } + } + + /** + * Clear the given watch. + * @param watchId watch identifier returned from [watchPosition] function + */ + @Suppress("UnsafeCastFromDynamic") + fun clearWatch(watchId: String) { + if (window.asDynamic().cordova.plugins.locationServices.geolocation != null) { + window.asDynamic().cordova.plugins.locationServices.geolocation.clearWatch(watchId) + } + } + + private fun getJsPriority(priority: Priority): dynamic { + return when (priority) { + Priority.PRIORITY_HIGH_ACCURACY -> 100 + Priority.PRIORITY_BALANCED_POWER_ACCURACY -> 102 + Priority.PRIORITY_LOW_POWER -> 104 + Priority.PRIORITY_NO_POWER -> 105 + } + } + +} -- cgit From bb065993c3155a48e8c4095bb39970879c414547 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sat, 8 Jun 2019 00:56:29 +0200 Subject: Support for Cordova InAppBrowser api. --- .../pl/treksoft/kvision/cordova/InAppBrowser.kt | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt new file mode 100644 index 00000000..c0c8f0b4 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * InAppBrowser event type. + */ +external class InAppBrowserEvent { + val type: String + val url: String + val code: Number? + val message: String? + val data: String? +} + +/** + * InAppBrowser reference object. + */ +external class Browser { + fun addEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit) + fun removeEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit) + fun close() + fun show() + fun hide() + fun executeScript(details: dynamic, callback: ((dynamic) -> Unit) = definedExternally) + fun insertCSS(details: dynamic, callback: (() -> Unit) = definedExternally) +} + +/** + * Main object for Cordova InAppBrowser api. + */ +object InAppBrowser { + + /** + * Open new browser window. + * @param url an URL address + * @param target a target window + * @param options a string with window options + * @return a browser window reference + */ + suspend fun open(url: String, target: String = "_blank", options: String? = null): Browser { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val ref = window.asDynamic().cordova.InAppBrowser.open(url, target, options) + continuation.resume(ref) + } + } + } + +} -- cgit From 6b32017e39015f6c4179018dd7a78542c0d79b10 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 20 Jun 2019 13:32:42 +0200 Subject: Support for Cordova media api. --- .../kotlin/pl/treksoft/kvision/cordova/Media.kt | 104 +++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt new file mode 100644 index 00000000..6ea43d76 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Media error class. + */ +external class MediaError { + val code: Int + val message: String + + companion object { + val MEDIA_ERR_ABORTED: Int + val MEDIA_ERR_NETWORK: Int + val MEDIA_ERR_DECODE: Int + val MEDIA_ERR_NONE_SUPPORTED: Int + } +} + +/** + * Cordova media class. + */ +external class Media( + src: String, + mediaSuccess: (() -> Unit)? = definedExternally, + mediaError: ((MediaError) -> Unit)? = definedExternally, + mediaStatus: ((Int) -> Unit)? = definedExternally +) { + + fun play() + fun pause() + fun stop() + fun startRecord() + fun pauseRecord() + fun resumeRecord() + fun stopRecord() + fun seekTo(millis: Number) + fun setRate(rate: Number) + fun setVolume(volume: Number) + fun getDuration(): Number + fun getCurrentAmplitude(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally) + fun getCurrentPosition(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally) + fun release() + + val position: Number + val duration: Number + + companion object { + val MEDIA_NONE: Int + val MEDIA_STARTING: Int + val MEDIA_RUNNING: Int + val MEDIA_PAUSED: Int + val MEDIA_STOPPED: Int + } +} + +/** + * Returns the current amplitude within an audio file. + */ +suspend fun Media.getCurrentAmplitude(): Number { + return suspendCoroutine { continuation -> + this.getCurrentAmplitude({ amp -> + continuation.resume(amp) + }, { + continuation.resume(-1) + }) + } +} + +/** + * Returns the current position within an audio file. + */ +suspend fun Media.getCurrentPosition(): Number { + return suspendCoroutine { continuation -> + this.getCurrentPosition({ pos -> + continuation.resume(pos) + }, { + continuation.resume(-1) + }) + } +} -- cgit From 35b3961a8280db4d8974a9c547cbabc06f02e364 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 20 Jun 2019 22:20:03 +0200 Subject: Support for Cordova media capture api. --- .../pl/treksoft/kvision/cordova/MediaCapture.kt | 196 +++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt new file mode 100644 index 00000000..c7b427dc --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.document +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Media error class. + */ +internal external class CaptureError { + val code: Int + + companion object { + val CAPTURE_INTERNAL_ERR: Int + val CAPTURE_APPLICATION_BUSY: Int + val CAPTURE_INVALID_ARGUMENT: Int + val CAPTURE_NO_MEDIA_FILES: Int + val CAPTURE_PERMISSION_DENIED: Int + val CAPTURE_NOT_SUPPORTED: Int + } +} + +/** + * Media file details class. + */ +external class MediaFileData { + val codecs: String? + val bitrate: Number + val height: Number + val width: Number + val duration: Number +} + +/** + * Media file information class. + */ +external class MediaFile { + val name: String + val fullPath: String + val type: String + val lastModifiedDate: Number + val size: Number + + fun getFormatData(successCallback: (MediaFileData) -> Unit, errorCallback: (() -> Unit)? = definedExternally) +} + +/** + * Get details for the given file. + */ +suspend fun MediaFile.getFormatData(): MediaFileData? { + return suspendCoroutine { continuation -> + this.getFormatData({ + continuation.resume(it) + }, { + continuation.resume(null) + }) + } +} + +/** + * Exception class for media capture errors. + */ +class CaptureException(val code: MediaCapture.CaptureErrorCode) : Exception("Capture exception: $code") + +/** + * Main media capture object. + */ +object MediaCapture { + + enum class CaptureErrorCode { + CAPTURE_INTERNAL_ERR, + CAPTURE_APPLICATION_BUSY, + CAPTURE_INVALID_ARGUMENT, + CAPTURE_NO_MEDIA_FILES, + CAPTURE_PERMISSION_DENIED, + CAPTURE_NOT_SUPPORTED + } + + private fun codeToEnum(code: Int): CaptureErrorCode { + return when (code) { + 0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR + 1 -> CaptureErrorCode.CAPTURE_APPLICATION_BUSY + 2 -> CaptureErrorCode.CAPTURE_INVALID_ARGUMENT + 3 -> CaptureErrorCode.CAPTURE_NO_MEDIA_FILES + 4 -> CaptureErrorCode.CAPTURE_PERMISSION_DENIED + else -> CaptureErrorCode.CAPTURE_NOT_SUPPORTED + } + } + + /** + * Capture audio file(s). + * @param limit The maximum number of audio clips the user can capture in a single capture operation. + * @param duration The maximum duration of an audio clip, in seconds. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureAudio(limit: Number = 1, duration: Number? = null): Result, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureAudio({ result: Array -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + if (duration != null) this.duration = duration + }) + } + } + } + + /** + * Capture image file(s). + * @param limit The maximum number of image files the user can capture in a single capture operation. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureImage(limit: Number = 1): Result, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureImage({ result: Array -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + }) + } + } + } + + /** + * Capture video file(s). + * @param limit The maximum number of video clips the user can capture in a single capture operation. + * @param duration The maximum duration of an video clip, in seconds. + * @param lowQuality Capture video with a lower quality (Android only) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureVideo( + limit: Number = 1, + duration: Number? = null, + lowQuality: Boolean = false + ): Result, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureVideo({ result: Array -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + if (duration != null) this.duration = duration + if (lowQuality) this.quality = 0 + }) + } + } + } + + /** + * Add listeners for pending capture Cordova events. + */ + fun addPendingCaptureListener(listener: (Result, CaptureException>) -> Unit) { + addDeviceReadyListener { + document.addEventListener("pendingcaptureresult", { result -> + @Suppress("CAST_NEVER_SUCCEEDS") + listener(Result.success((result as? Array)?.asList() ?: listOf())) + }, false) + document.addEventListener("pendingcaptureerror", { error -> + @Suppress("CAST_NEVER_SUCCEEDS") + listener(Result.error(CaptureException(codeToEnum((error as? CaptureError)?.code ?: 0)))) + }, false) + } + } +} -- cgit From 5082d9ec1507ebeca74b30e4f46a034ed688b46d Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 20 Jun 2019 22:22:19 +0200 Subject: Change result builder. --- .../src/main/kotlin/pl/treksoft/kvision/cordova/File.kt | 4 ++-- .../src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt index df176e84..6e7ba732 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt @@ -106,7 +106,7 @@ object File { return suspendCoroutine { continuation -> addDeviceReadyListener { window.asDynamic().resolveLocalFileSystemURL(url, { entry -> - continuation.resume(Result.Success(entry)) + continuation.resume(Result.success(entry)) }, { error -> continuation.resume(Result.error(FileException(error.code))) }) @@ -173,7 +173,7 @@ object File { } addDeviceReadyListener { window.asDynamic().requestFileSystem(type, size, { fs -> - continuation.resume(Result.Success(fs)) + continuation.resume(Result.success(fs)) }, { error -> continuation.resume(Result.error(FileException(error.code))) }) diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt index 9f4d9cfe..4087a4f2 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt @@ -90,7 +90,7 @@ object Geolocation { return suspendCoroutine { continuation -> addDeviceReadyListener { window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position -> - continuation.resume(Result.Success(position)) + continuation.resume(Result.success(position)) }, { error -> continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) }, obj { @@ -120,7 +120,7 @@ object Geolocation { return suspendCoroutine { continuation -> addDeviceReadyListener { val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position -> - resultCallback(Result.Success(position)) + resultCallback(Result.success(position)) }, { error -> resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) }, obj { @@ -183,7 +183,7 @@ object Locationservices { addDeviceReadyListener { window.asDynamic() .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position -> - continuation.resume(Result.Success(position)) + continuation.resume(Result.success(position)) }, { error -> continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) }, obj { @@ -224,7 +224,7 @@ object Locationservices { val watchId = window.asDynamic() .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position -> - resultCallback(Result.Success(position)) + resultCallback(Result.success(position)) }, { error -> resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) }, obj { -- cgit From d9f1a90c772719d14540eb2bf7bc3b8384fa7a72 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 20 Jun 2019 22:33:54 +0200 Subject: Suppress some style warnings. --- .../src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt | 1 + .../kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt | 1 + .../src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt | 1 + 3 files changed, 3 insertions(+) (limited to 'kvision-modules/kvision-cordova') diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt index 4087a4f2..c4ccd354 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt @@ -251,6 +251,7 @@ object Locationservices { } } + @Suppress("MagicNumber") private fun getJsPriority(priority: Priority): dynamic { return when (priority) { Priority.PRIORITY_HIGH_ACCURACY -> 100 diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt index 6ea43d76..a126941b 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt @@ -43,6 +43,7 @@ external class MediaError { /** * Cordova media class. */ +@Suppress("TooManyFunctions") external class Media( src: String, mediaSuccess: (() -> Unit)? = definedExternally, diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt index c7b427dc..fda81da7 100644 --- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt @@ -100,6 +100,7 @@ object MediaCapture { CAPTURE_NOT_SUPPORTED } + @Suppress("MagicNumber") private fun codeToEnum(code: Int): CaptureErrorCode { return when (code) { 0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR -- cgit