aboutsummaryrefslogtreecommitdiff
path: root/kvision-modules/kvision-cordova/src/main
diff options
context:
space:
mode:
authorRobbie Cronin <robert.cronin@uqconnect.edu.au>2019-06-21 13:17:30 +0800
committerGitHub <noreply@github.com>2019-06-21 13:17:30 +0800
commit2f8e0060308b0f5b1eeac9541600bfed3ad5acc9 (patch)
tree80d40364c5f5957da8c82578dd1152adac06912e /kvision-modules/kvision-cordova/src/main
parent9038555f9e46a32a0a725112304e64cdd408bf8e (diff)
parentd9f1a90c772719d14540eb2bf7bc3b8384fa7a72 (diff)
downloadkvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.tar.gz
kvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.tar.bz2
kvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.zip
Merge pull request #1 from rjaros/master
merge
Diffstat (limited to 'kvision-modules/kvision-cordova/src/main')
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt73
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt270
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt139
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt542
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt221
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt47
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt264
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt74
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt105
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt197
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt89
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt106
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt154
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt76
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt50
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt154
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt43
17 files changed, 2604 insertions, 0 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
new file mode 100644
index 00000000..656a519a
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -0,0 +1,73 @@
+/*
+ * 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
+
+/**
+ * Battery status.
+ */
+external class BatteryStatus {
+ val level: Int
+ val isPlugged: Boolean
+}
+
+/**
+ * Main object for Cordova battery.
+ */
+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)
+ }
+ }
+
+ /**
+ * Get battery status.
+ */
+ suspend fun getStatus(): BatteryStatus {
+ return suspendCoroutine { continuation ->
+ addStatusListener(Battery.BatteryEvent.BATTERY_STATUS) { status ->
+ continuation.resume(status)
+ }
+ }
+ }
+}
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..40c731c7
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
@@ -0,0 +1,270 @@
+/*
+ * 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
+)
+
+@Suppress("ComplexMethod")
+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
new file mode 100644
index 00000000..d83b79d4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.w3c.dom.events.Event
+import kotlin.browser.document
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Device information class.
+ */
+external class Device {
+ 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?
+}
+
+/**
+ * Cordova event types.
+ */
+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
+
+/**
+ * Cordova device information object.
+ */
+var cordovaDevice: Device? = null
+ private set
+
+/**
+ * Add listeners for 'deviceready' Cordova event.
+ */
+fun addDeviceReadyListener(listener: (Device) -> Unit) {
+ document.addEventListener(CordovaEvent.DEVICEREADY.type, {
+ listener(device)
+ }, false)
+}
+
+/**
+ * Add listeners for 'pause' Cordova event.
+ */
+fun addPauseListener(listener: () -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener(CordovaEvent.PAUSE.type, {
+ listener()
+ }, false)
+ }
+}
+
+/**
+ * Add listeners for 'resume' Cordova event.
+ */
+fun addResumeListener(listener: (ResumeEvent) -> Unit) {
+ 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 cordovaDevice ?: suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ cordovaDevice = device
+ continuation.resume(device)
+ }
+ }
+}
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..6e7ba732
--- /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<Entry, FileException> {
+ 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<DirectoryEntry, FileException> {
+ 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<FileEntry, FileException> {
+ 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<FileSystem, FileException> {
+ 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<DirectoryEntry, FileException> {
+ return File.resolveLocalFileSystemURLForDir(this)
+}
+
+/**
+ * Extension function to convert String to a file entry.
+ */
+suspend fun String.toFileEntry(): Result<FileEntry, FileException> {
+ return File.resolveLocalFileSystemURLForFile(this)
+}
+
+/**
+ * Get file or directory metadata.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.getMetadata(): Result<Metadata, FileException> {
+ 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<DirectoryEntry, FileException> {
+ 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<Entry, FileException> {
+ 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<Entry, FileException> {
+ 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<Entry, FileException> {
+ 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<FileWriter, FileException> {
+ 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<org.w3c.files.File, FileException> {
+ 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<FileEntry, FileException> {
+ 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<DirectoryEntry, FileException> {
+ 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<DirectoryEntry, FileException> {
+ 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<List<Entry>, FileException> {
+ return suspendCoroutine { continuation ->
+ this.readEntries({ entries: Array<Entry> ->
+ 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<List<Entry>, FileException> {
+ return this.createReader().readEntries()
+}
+
+/**
+ * Read file content as a plain string.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsText(): Result<String, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<String, FileException>> { 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<String, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<String, FileException>> { 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<ArrayBuffer, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<ArrayBuffer, FileException>> { 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<FileEntry, FileException> {
+ return this.createWriter().flatMap { writer ->
+ suspendCoroutine<Result<FileEntry, FileException>> { 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<FileEntry, FileException> {
+ 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<FileEntry, FileException> {
+ 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<FileEntry, FileException> {
+ 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<FileEntry, FileException> {
+ return this.createWriter().flatMap { writer ->
+ try {
+ writer.seek(writer.length)
+ } catch (e: Exception) {
+ console.log("File doesn't exist!")
+ }
+ suspendCoroutine<Result<FileEntry, FileException>> { 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<FileEntry, FileException> {
+ 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<Entry>) {
+ 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<EntrySync>
+}
+
+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/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
new file mode 100644
index 00000000..c4ccd354
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
@@ -0,0 +1,264 @@
+/*
+ * 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<Position, GeolocationException> {
+ 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<Position, GeolocationException>) -> 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<Position, GeolocationException> {
+ 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<Position, GeolocationException>) -> 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)
+ }
+ }
+
+ @Suppress("MagicNumber")
+ 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
+ }
+ }
+
+}
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)
+ }
+ }
+ }
+
+}
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..a126941b
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+@Suppress("TooManyFunctions")
+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)
+ })
+ }
+}
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..fda81da7
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
@@ -0,0 +1,197 @@
+/*
+ * 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
+ }
+
+ @Suppress("MagicNumber")
+ 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<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureAudio({ result: Array<MediaFile> ->
+ 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<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureImage({ result: Array<MediaFile> ->
+ 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<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureVideo({ result: Array<MediaFile> ->
+ 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<List<MediaFile>, CaptureException>) -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener("pendingcaptureresult", { result ->
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ listener(Result.success((result as? Array<MediaFile>)?.asList() ?: listOf()))
+ }, false)
+ document.addEventListener("pendingcaptureerror", { error ->
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ listener(Result.error(CaptureException(codeToEnum((error as? CaptureError)?.code ?: 0))))
+ }, false)
+ }
+ }
+}
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)
+ }
+ }
+
+}
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<String>? = 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<String>? = 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)
+ }
+ }
+
+}
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..83a1987e
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
@@ -0,0 +1,154 @@
+@file:Suppress(
+ "TooManyFunctions", "TooGenericExceptionCaught"
+)
+/*
+ * 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
+}
+
+inline fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
+
+inline 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)
+ }
+ }
+
+}
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)
+ }
+}
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()
+ }
+ }
+
+}
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..e3de6af2
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
@@ -0,0 +1,154 @@
+/*
+ * 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
+
+@Suppress("TopLevelPropertyNaming")
+@JsName("StatusBar")
+internal external val StatusBarJs: dynamic
+
+/**
+ * Main object for Cordova status bar.
+ */
+@Suppress("TooManyFunctions")
+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()
+ })
+ }
+ }
+
+}
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)
+ }
+ }
+
+}