Skip to content

feat: Implement cordova plugin #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 28, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
node_modules
dist
/dist
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### 2025-03-21

- Feat: Implement plugin methods: `openDocumentFromLocalPath`, `openDocumentFromResources`, `openDocumentFromUrl`, `previewMediaContentFromLocalPath`, `previewMediaContentFromResources`, `previewMediaContentFromUrl`.
- Feat: Add outsystems-wrapper block.
171 changes: 171 additions & 0 deletions packages/cordova-plugin/README.md
Original file line number Diff line number Diff line change
@@ -2,10 +2,181 @@

*This plugin is SUPPORTED by OutSystems. Customers entitled to Support Services may obtain assistance through Support.*

This plugin is only available in Native Android and iOS; not available for Web / PWAs.

## Installation

```console
cordova plugin add <path-to-repo-local-clone>
```

## API

<docgen-index>

* [`openDocumentFromLocalPath(...)`](#opendocumentfromlocalpath)
* [`openDocumentFromResources(...)`](#opendocumentfromresources)
* [`openDocumentFromUrl(...)`](#opendocumentfromurl)
* [`previewMediaContentFromLocalPath(...)`](#previewmediacontentfromlocalpath)
* [`previewMediaContentFromResources(...)`](#previewmediacontentfromresources)
* [`previewMediaContentFromUrl(...)`](#previewmediacontentfromurl)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)

</docgen-index>


<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->

File Viewer API

Only available in Native Android and iOS; not available for Web / PWAs.

### openDocumentFromLocalPath(...)

```typescript
openDocumentFromLocalPath(options: OpenFromLocalPathOptions) => Promise<void>
```

Open a file stored in the local file system

| Param | Type |
| ------------- | ----------------------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromlocalpathoptions">OpenFromLocalPathOptions</a></code> |

**Since:** 1.0.0

--------------------


### openDocumentFromResources(...)

```typescript
openDocumentFromResources(options: OpenFromResourcesOptions) => Promise<void>
```

Open an app resource file

| Param | Type |
| ------------- | ----------------------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromresourcesoptions">OpenFromResourcesOptions</a></code> |

**Since:** 1.0.0

--------------------


### openDocumentFromUrl(...)

```typescript
openDocumentFromUrl(options: OpenFromUrlOptions) => Promise<void>
```

Open a file from a remote url

| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromurloptions">OpenFromUrlOptions</a></code> |

**Since:** 1.0.0

--------------------


### previewMediaContentFromLocalPath(...)

```typescript
previewMediaContentFromLocalPath(options: PreviewMediaFromLocalPathOptions) => Promise<void>
```

Preview a media file (namely, video) stored in the local file system.
Only implemented in iOS. Android defaults to `openDocumentFromLocalPath`.

| Param | Type |
| ------------- | ----------------------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromlocalpathoptions">OpenFromLocalPathOptions</a></code> |

**Since:** 1.0.0

--------------------


### previewMediaContentFromResources(...)

```typescript
previewMediaContentFromResources(options: PreviewMediaFromResourcesOptions) => Promise<void>
```

Preview a media file (namely, video) from the app's resources.
Only implemented in iOS. Android defaults to `openDocumentFromResources`.

| Param | Type |
| ------------- | ----------------------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromresourcesoptions">OpenFromResourcesOptions</a></code> |

**Since:** 1.0.0

--------------------


### previewMediaContentFromUrl(...)

```typescript
previewMediaContentFromUrl(options: PreviewMediaFromUrlOptions) => Promise<void>
```

Preview a media file (namely, video) from a remote url.
Only implemented in iOS. Android defaults to `openDocumentFromUrl`.

| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#openfromurloptions">OpenFromUrlOptions</a></code> |

**Since:** 1.0.0

--------------------


### Interfaces


#### OpenFromLocalPathOptions

| Prop | Type | Description | Since |
| ---------- | ------------------- | ------------------------------------------ | ----- |
| **`path`** | <code>string</code> | The full absolute path to the file to open | 1.0.0 |


#### OpenFromResourcesOptions

| Prop | Type | Description | Since |
| ---------- | ------------------- | ---------------------------------------------- | ----- |
| **`path`** | <code>string</code> | The relative path to the resource file to open | 1.0.0 |


#### OpenFromUrlOptions

| Prop | Type | Description | Since |
| --------- | ------------------- | ------------------------------------------- | ----- |
| **`url`** | <code>string</code> | The remote url pointing to the file to open | 1.0.0 |


### Type Aliases


#### PreviewMediaFromLocalPathOptions

<code><a href="#openfromlocalpathoptions">OpenFromLocalPathOptions</a></code>


#### PreviewMediaFromResourcesOptions

<code><a href="#openfromresourcesoptions">OpenFromResourcesOptions</a></code>


#### PreviewMediaFromUrlOptions

<code><a href="#openfromurloptions">OpenFromUrlOptions</a></code>

</docgen-api>
68 changes: 68 additions & 0 deletions packages/cordova-plugin/android/OSFileViewerErrors.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.outsystems.plugins.fileviewer

import io.ionic.libs.ionfileviewerlib.model.IONFLVWException

object OSFileViewerErrors {
private fun formatErrorCode(number: Int): String {
return "OS-PLUG-FLVW-" + number.toString().padStart(4, '0')
}

data class ErrorInfo(
val code: String,
val message: String
)

val fileDoesNotExist = ErrorInfo(
code = formatErrorCode(4),
message = "The file you are trying to open does not exist."
)

fun urlMalformed(url: String) = if (url.isBlank()) {
urlEmpty
} else {
ErrorInfo(
code = formatErrorCode(5),
message = "The URL you are trying to open is malformed - $url"
)
}

fun filePathInvalid(path: String?) = if (path.isNullOrBlank()) {
filePathEmpty
} else {
invalidParameters
}

val filePathEmpty = ErrorInfo(
code = formatErrorCode(6),
message = "Path of the file to open is either null or empty."
)

val urlEmpty = ErrorInfo(
code = formatErrorCode(7),
message = "URL to open is either null or empty."
)

val genericError = ErrorInfo(
code = formatErrorCode(8),
message = "Could not open the document."
)

val invalidParameters: ErrorInfo = ErrorInfo(
code = formatErrorCode(9),
message = "Invalid parameters."
)

val noAppToOpen = ErrorInfo(
code = formatErrorCode(10),
message = "There is no app to open this document."
)
}

fun Throwable.toOSFileViewerError(): OSFileViewerErrors.ErrorInfo = when (this) {
is IONFLVWException.FileDoesNotExist -> OSFileViewerErrors.fileDoesNotExist
is IONFLVWException.InvalidURL -> OSFileViewerErrors.urlMalformed(url)
is IONFLVWException.InvalidPath -> OSFileViewerErrors.filePathInvalid(path)
is IONFLVWException.EmptyURL -> OSFileViewerErrors.urlEmpty
is IONFLVWException.NoApp -> OSFileViewerErrors.noAppToOpen
else -> OSFileViewerErrors.genericError
}
98 changes: 85 additions & 13 deletions packages/cordova-plugin/android/OSFileViewerPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
package com.outsystems.plugins.fileviewer

import io.ionic.libs.ionfileviewerlib.IONFLVWController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.apache.cordova.CallbackContext
import org.apache.cordova.CordovaInterface
import org.apache.cordova.CordovaPlugin
import org.apache.cordova.CordovaWebView
import org.apache.cordova.PluginResult
import org.json.JSONArray
import org.json.JSONObject

/**
* Cordova bridge, inherits from CordovaPlugin
*/
class OSFileViewerPlugin : CordovaPlugin() {

private lateinit var coroutineScope: CoroutineScope

override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) {
super.initialize(cordova, webView)
coroutineScope = CoroutineScope(Dispatchers.IO)
}
private val controller by lazy { IONFLVWController() }
private val coroutineScope by lazy { CoroutineScope(Dispatchers.IO) }

override fun onDestroy() {
super.onDestroy()
@@ -32,6 +25,85 @@ class OSFileViewerPlugin : CordovaPlugin() {
args: JSONArray,
callbackContext: CallbackContext
): Boolean {
return true
val optionsObject: JSONObject
try {
optionsObject = args.getJSONObject(0)
} catch (e: Exception) {
callbackContext.sendError(OSFileViewerErrors.invalidParameters)
return true
}

return when (action) {
"openDocumentFromLocalPath",
"previewMediaContentFromLocalPath" -> {
openDocumentFromLocalPath(optionsObject, callbackContext)
true
}

"openDocumentFromResources",
"previewMediaContentFromResources" -> {
openDocumentFromResources(optionsObject, callbackContext)
true
}

"openDocumentFromUrl",
"previewMediaContentFromUrl" -> {
openDocumentFromUrl(optionsObject, callbackContext)
true
}

else -> false
}
}

private fun openDocumentFromLocalPath(options: JSONObject, callbackContext: CallbackContext) {
val filePath: String? = options.optString("path")
controller.openDocumentFromLocalPath(cordova.activity, filePath ?: "")
.onSuccess {
callbackContext.success()
}
.onFailure {
callbackContext.sendError(it.toOSFileViewerError())
}
}

private fun openDocumentFromResources(options: JSONObject, callbackContext: CallbackContext) {
val resourcePath: String? = options.optString("path")
coroutineScope.launch {
// this native library method needs to be run from a separate thread, to avoid freezing the UI.
controller.openDocumentFromResources(cordova.activity, resourcePath ?: "")
.onSuccess {
callbackContext.success()
}
.onFailure {
callbackContext.sendError(it.toOSFileViewerError())
}
}
}

private fun openDocumentFromUrl(options: JSONObject, callbackContext: CallbackContext) {
val url: String? = options.optString("url")
controller.openDocumentFromUrl(cordova.activity, url ?: "")
.onSuccess {
callbackContext.success()
}
.onFailure {
callbackContext.sendError(it.toOSFileViewerError())
}
}

/**
* Extension function to return a unsuccessful plugin result
* @param error error class representing the error to return, containing a code and message
*/
private fun CallbackContext.sendError(error: OSFileViewerErrors.ErrorInfo) {
val pluginResult = PluginResult(
PluginResult.Status.ERROR,
JSONObject().apply {
put("code", error.code)
put("message", error.message)
}
)
this.sendPluginResult(pluginResult)
}
}
Loading