aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt6
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt269
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt52
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt151
4 files changed, 467 insertions, 11 deletions
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<String, CameraException> {
+ 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<String, CameraException>) -> 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<String, CameraException>) -> 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<String, CameraException>) -> 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?
}
/**
@@ -57,6 +74,25 @@ fun addDeviceReadyListener(listener: (Device) -> Unit) {
}
/**
+ * 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.
*/
suspend fun getDevice(): Device {
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 <reified X> Result<*, *>.getAs() = when (this) {
+ is Result.Success -> value as? X
+ is Result.Failure -> error as? X
+}
+
+fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
+
+fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f)
+
+infix fun <V : Any, E : Exception> Result<V, E>.or(fallback: V) = when (this) {
+ is Result.Success -> this
+ else -> Result.Success(fallback)
+}
+
+infix fun <V : Any, E : Exception> Result<V, E>.getOrElse(fallback: V) = when (this) {
+ is Result.Success -> value
+ else -> fallback
+}
+
+inline fun <V : Any, U : Any, E : Exception> Result<V, E>.map(transform: (V) -> U): Result<U, E> = 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 <V : Any, U : Any, E : Exception> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> = 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 <V : Any, E : Exception, E2 : Exception> Result<V, E>.mapError(transform: (E) -> E2) = when (this) {
+ is Result.Success -> Result.Success(value)
+ is Result.Failure -> Result.Failure(transform(error))
+}
+
+fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.flatMapError(transform: (E) -> Result<V, E2>) = when (this) {
+ is Result.Success -> Result.Success(value)
+ is Result.Failure -> transform(error)
+}
+
+fun <V : Any, E : Exception> Result<V, E>.any(predicate: (V) -> Boolean): Boolean = try {
+ when (this) {
+ is Result.Success -> predicate(value)
+ is Result.Failure -> false
+ }
+} catch (ex: Exception) {
+ false
+}
+
+fun <V : Any, U : Any> Result<V, *>.fanout(other: () -> Result<U, *>): Result<Pair<V, U>, *> =
+ flatMap { outer -> other().map { outer to it } }
+
+sealed class Result<out V : Any, out E : Exception> {
+
+ open operator fun component1(): V? = null
+ open operator fun component2(): E? = null
+
+ inline fun <X> 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<out V : Any>(val value: V) : Result<V, Nothing>() {
+ 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<out E : Exception>(val error: E) : Result<Nothing, E>() {
+ 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 <E : Exception> error(ex: E) = Failure(ex)
+
+ fun <V : Any> success(v: V) = Success(v)
+
+ fun <V : Any> of(value: V?, fail: (() -> Exception) = { Exception() }): Result<V, Exception> =
+ value?.let { success(it) } ?: error(fail())
+
+ fun <V : Any, E : Exception> of(f: () -> V): Result<V, E> = try {
+ success(f())
+ } catch (ex: Exception) {
+ @Suppress("UNCHECKED_CAST")
+ error(ex as E)
+ }
+ }
+
+}