diff --git a/.changeset/dirty-books-prove.md b/.changeset/dirty-books-prove.md
new file mode 100644
index 00000000..b9ff3b23
--- /dev/null
+++ b/.changeset/dirty-books-prove.md
@@ -0,0 +1,5 @@
+---
+"@capacitor/background-runner": major
+---
+
+Adding a new App API, as well as app badge manipulation methods to the Notifications API
diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml
index f54ffd15..c5fa4e82 100644
--- a/.github/actions/setup-tools/action.yml
+++ b/.github/actions/setup-tools/action.yml
@@ -22,7 +22,7 @@ runs:
java-version: '17'
- name: Install PNPM
- uses: pnpm/action-setup@v2
+ uses: pnpm/action-setup@v4
id: pnpm-install
with:
version: 8
diff --git a/README.md b/README.md
index 50e56cec..9f2e0579 100644
--- a/README.md
+++ b/README.md
@@ -441,9 +441,11 @@ A simple string key / value store backed by UserDefaults on iOS and Shared Prefe
Send basic local notifications.
-| Prop | Type | Description | Since |
-| -------------- | ------------------------------------- | ----------------------------- | ----- |
-| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| Prop | Type | Description | Since |
+| ---------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------- | ----- |
+| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| **`setBadge`** | (options: NotificationBadgeOptions) => void
| Set the application badge count | 2.0.0 |
+| **`clearBadge`** | () => void
| Clears the application badge count | 2.0.0 |
#### NotificationScheduleOptions
@@ -470,6 +472,15 @@ Send basic local notifications.
| **`channelId`** | string
| Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls `setChannelId()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android 26+. | 1.0.0 |
+#### NotificationBadgeOptions
+
+| Prop | Type | Description | Since |
+| -------------------------- | ------------------- | ------------------------------------------------------------------------------------- | ----- |
+| **`count`** | number
| The number to set on the application badge count. | 2.0.0 |
+| **`notificationTitle`** | string
| The **required** title for the associated badge count notification. Only for Android. | 2.0.0 |
+| **`notificationSubtitle`** | string
| The subtitle for the associated badge count notification. Only for Android. | 2.0.0 |
+
+
#### CapacitorGeolocation
Get access to device location information.
@@ -496,7 +507,7 @@ Get access to device location information.
Interact with a watch paired with this app
-sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application.
+sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapacitorWatch Watch application.
They could be used if a native watch app is developed as a companion app to a Capacitor app
| Prop | Type | Description |
@@ -509,4 +520,29 @@ They could be used if a native watch app is developed as a companion app to a Ca
| **`updateWatchData`** | (options: { data: { [key: string]: string; }; }) => void
| Updates the data the watch is using to display variables in text and button fields |
+#### CapacitorApp
+
+| Prop | Type |
+| -------------- | ------------------------------------------------------ |
+| **`getState`** | () => AppState
|
+| **`getInfo`** | () => AppInfo
|
+
+
+#### AppState
+
+| Prop | Type | Description | Since |
+| -------------- | -------------------- | --------------------------------- | ----- |
+| **`isActive`** | boolean
| Whether the app is active or not. | 1.0.0 |
+
+
+#### AppInfo
+
+| Prop | Type | Description | Since |
+| ------------- | ------------------- | --------------------------------------------------------------------------------------------------- | ----- |
+| **`name`** | string
| The name of the app. | 1.0.0 |
+| **`id`** | string
| The identifier of the app. On iOS it's the Bundle Identifier. On Android it's the Application ID | 1.0.0 |
+| **`build`** | string
| The build version. On iOS it's the CFBundleVersion. On Android it's the versionCode. | 1.0.0 |
+| **`version`** | string
| The app version. On iOS it's the CFBundleShortVersionString. On Android it's package's versionName. | 1.0.0 |
+
+
diff --git a/apps/example-app/src/background.js b/apps/example-app/src/background.js
index c3dbcfb0..0c7df9fd 100644
--- a/apps/example-app/src/background.js
+++ b/apps/example-app/src/background.js
@@ -153,6 +153,63 @@ addEventListener(
}
);
+addEventListener("testCapacitorAppGetBadge", (resolve, reject, args) => {
+ try {
+ const value = CapacitorNotifications.getBadge();
+ console.log(JSON.stringify(value));
+ resolve(value);
+ } catch (err) {
+ console.error(err);
+ reject(err);
+ }
+});
+
+addEventListener("testCapacitorAppSetBadge", (resolve, reject, args) => {
+ try {
+ CapacitorNotifications.setBadge({
+ count: 55,
+ notificationTitle: "You have new messages",
+ notificationSubtitle: "testing, testing, 1, 2, 3",
+ });
+ resolve();
+ } catch (err) {
+ console.error(err);
+ reject(err);
+ }
+});
+
+addEventListener("testCapacitorAppClearBadge", (resolve, reject, args) => {
+ try {
+ CapacitorNotifications.clearBadge();
+ resolve();
+ } catch (err) {
+ console.error(err);
+ reject(err);
+ }
+});
+
+addEventListener("testCapacitorAppGetInfo", (resolve, reject, args) => {
+ try {
+ const info = CapacitorApp.getInfo();
+ console.log(JSON.stringify(info));
+ resolve(info);
+ } catch (err) {
+ console.error(err);
+ reject(err);
+ }
+});
+
+addEventListener("testCapacitorAppGetState", (resolve, reject, args) => {
+ try {
+ const state = CapacitorApp.getState();
+ console.log(JSON.stringify(state));
+ resolve(state);
+ } catch (err) {
+ console.error(err);
+ reject(err);
+ }
+});
+
addEventListener("remoteNotification", (resolve, reject, args) => {
console.log("received silent push notification");
diff --git a/apps/example-app/src/pages/Tab1.tsx b/apps/example-app/src/pages/Tab1.tsx
index 9f6067d6..bb006264 100644
--- a/apps/example-app/src/pages/Tab1.tsx
+++ b/apps/example-app/src/pages/Tab1.tsx
@@ -11,7 +11,6 @@ import {
import { BackgroundRunner } from "@capacitor/background-runner";
import "./Tab1.css";
-
const Tab1: React.FC = () => {
const [commandOutput, setCommandOutput] = useState("");
@@ -44,12 +43,10 @@ const Tab1: React.FC = () => {
label: "com.example.background.task",
event: "testCapKVSet",
details: {
- value: "Hello World"
+ value: "Hello World",
},
});
- setCommandOutput(
- `success: stored value 'Hello World'`
- );
+ setCommandOutput(`success: stored value 'Hello World'`);
} catch (err) {
setCommandOutput(`ERROR: ${err}`);
}
@@ -63,9 +60,7 @@ const Tab1: React.FC = () => {
event: "testCapKVGet",
details: {},
});
- setCommandOutput(
- `success: retrieved ${JSON.stringify(response)}`
- );
+ setCommandOutput(`success: retrieved ${JSON.stringify(response)}`);
} catch (err) {
setCommandOutput(`ERROR: ${err}`);
}
@@ -77,18 +72,13 @@ const Tab1: React.FC = () => {
await BackgroundRunner.dispatchEvent({
label: "com.example.background.task",
event: "testCapKVRemove",
- details: {
-
- },
+ details: {},
});
- setCommandOutput(
- `success: value removed`
- );
+ setCommandOutput(`success: value removed`);
} catch (err) {
setCommandOutput(`ERROR: ${err}`);
}
};
-
const onTestCapNotification = async () => {
setCommandOutput("");
@@ -146,6 +136,62 @@ const Tab1: React.FC = () => {
}
};
+ const onTestCapAppSetBadge = async () => {
+ setCommandOutput("");
+ try {
+ await BackgroundRunner.dispatchEvent({
+ label: "com.example.background.task",
+ event: "testCapacitorAppSetBadge",
+ details: {},
+ });
+ setCommandOutput(`success`);
+ } catch (err) {
+ setCommandOutput(`ERROR: ${err}`);
+ }
+ };
+
+ const onTestCapAppClearBadge = async () => {
+ setCommandOutput("");
+ try {
+ await BackgroundRunner.dispatchEvent({
+ label: "com.example.background.task",
+ event: "testCapacitorAppClearBadge",
+ details: {},
+ });
+ setCommandOutput(`success`);
+ } catch (err) {
+ setCommandOutput(`ERROR: ${err}`);
+ }
+ };
+
+ const onTestCapAppGetInfo = async () => {
+ setCommandOutput("");
+ try {
+ const response = await BackgroundRunner.dispatchEvent({
+ label: "com.example.background.task",
+ event: "testCapacitorAppGetInfo",
+ details: {},
+ });
+ setCommandOutput(`success: ${JSON.stringify(response)}`);
+ } catch (err) {
+ setCommandOutput(`ERROR: ${err}`);
+ }
+ };
+
+ const onTestCapAppGetState = async () => {
+ setCommandOutput("");
+ try {
+ const response = await BackgroundRunner.dispatchEvent({
+ label: "com.example.background.task",
+ event: "testCapacitorAppGetState",
+ details: {},
+ });
+ setCommandOutput(`success: ${JSON.stringify(response)}`);
+ } catch (err) {
+ setCommandOutput(`ERROR: ${err}`);
+ }
+ };
+
return (
@@ -166,17 +212,37 @@ const Tab1: React.FC = () => {
autoGrow={true}
>
-
+
Check API Permissions
-
+
Request API Permissions
- Capacitor KV - Set
- Capacitor KV - Get
- Capacitor KV - Delete
+
+ Capacitor KV - Set
+
+
+ Capacitor KV - Get
+
+
+ Capacitor KV - Delete
+
- Test Capacitor Notification
+ Test Capacitor Notif.- Schedule Notification
+
+
+ Test Capacitor Notif. - Set Badge
+
+
+ Test Capacitor Notif. - Clear Badge
Test Capacitor Geolocation
@@ -187,6 +253,12 @@ const Tab1: React.FC = () => {
Test Capacitor Device - Network
+
+ Test Capacitor App - Get App Info
+
+
+ Test Capacitor App - Get App State
+
);
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.cpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.cpp
index 5b295221..b203211f 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.cpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.cpp
@@ -102,6 +102,13 @@ Java::Java(JNIEnv *env) {
env->DeleteLocalRef(tmp_class);
tmp_class = nullptr;
+ tmp_class = env->FindClass("io/ionic/android_js_engine/capacitor_api/AppAPI");
+ this->check_exception(env);
+
+ this->capacitor_app_api_class = (jclass)env->NewGlobalRef(tmp_class);
+ env->DeleteLocalRef(tmp_class);
+ tmp_class = nullptr;
+
this->capacitor_api_kv_field = env->GetFieldID(this->capacitor_api_class, "kv", "Lio/ionic/android_js_engine/capacitor_api/KVAPI;");
this->check_exception(env);
@@ -114,6 +121,9 @@ Java::Java(JNIEnv *env) {
this->capacitor_api_notification_field = env->GetFieldID(this->capacitor_api_class, "notifications", "Lio/ionic/android_js_engine/capacitor_api/NotificationsAPI;");
this->check_exception(env);
+ this->capacitor_api_app_field = env->GetFieldID(this->capacitor_api_class, "app", "Lio/ionic/android_js_engine/capacitor_api/AppAPI;");
+ this->check_exception(env);
+
this->capacitor_api_kv_set_method = env->GetMethodID(this->capacitor_kv_api_class, "set", "(Ljava/lang/String;Ljava/lang/String;)V");
this->check_exception(env);
@@ -134,6 +144,18 @@ Java::Java(JNIEnv *env) {
this->capacitor_api_notifications_schedule_method = env->GetMethodID(this->capacitor_notification_api_class, "schedule", "(Ljava/lang/String;)V");
this->check_exception(env);
+
+ this->capacitor_api_notifications_setBadge_method = env->GetMethodID(capacitor_notification_api_class, "setBadge", "(Ljava/lang/String;)V");
+ this->check_exception(env);
+
+ this->capacitor_api_notifications_clearBadge_method = env->GetMethodID(this->capacitor_notification_api_class, "clearBadge", "()V");
+ this->check_exception(env);
+
+ this->capacitor_api_app_getState_method = env->GetMethodID(this->capacitor_app_api_class, "getState", "()Ljava/lang/String;");
+ this->check_exception(env);
+
+ this->capacitor_api_app_getInfo_method = env->GetMethodID(this->capacitor_app_api_class, "getInfo", "()Ljava/lang/String;");
+ this->check_exception(env);
}
JNIEnv *Java::getEnv() {
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.h b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.h
index d0075252..4d39307e 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.h
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/java.h
@@ -16,6 +16,7 @@ class Java {
jclass capacitor_notification_api_class;
jclass capacitor_device_api_class;
jclass capacitor_geolocation_api_class;
+ jclass capacitor_app_api_class;
jmethodID web_api_fetch_method;
jmethodID web_api_byteArrayToString_method;
@@ -32,6 +33,10 @@ class Java {
jmethodID capacitor_api_device_getNetworkStatus_method;
jmethodID capacitor_api_geolocation_getCurrentPosition_method;
jmethodID capacitor_api_notifications_schedule_method;
+ jmethodID capacitor_api_notifications_setBadge_method;
+ jmethodID capacitor_api_notifications_clearBadge_method;
+ jmethodID capacitor_api_app_getInfo_method;
+ jmethodID capacitor_api_app_getState_method;
jfieldID native_js_response_ok_field;
jfieldID native_js_response_status_field;
@@ -43,6 +48,7 @@ class Java {
jfieldID capacitor_api_device_field;
jfieldID capacitor_api_notification_field;
jfieldID capacitor_api_geolocation_field;
+ jfieldID capacitor_api_app_field;
Java(JNIEnv *env);
JNIEnv *getEnv();
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/CMakeLists.txt b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/CMakeLists.txt
index b3e92bc6..560edf13 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/CMakeLists.txt
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/CMakeLists.txt
@@ -20,6 +20,7 @@ add_library(
src/capacitor-api/api_geolocation.cpp
src/capacitor-api/api_kv.cpp
src/capacitor-api/api_notifications.cpp
+ src/capacitor-api/api_app.cpp
)
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.cpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.cpp
new file mode 100644
index 00000000..e05c3c14
--- /dev/null
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.cpp
@@ -0,0 +1,43 @@
+#include "api_app.h"
+
+#include "../context.hpp"
+
+JSValue api_app_get_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
+ auto *context = (Context *)JS_GetContextOpaque(ctx);
+ if (context == nullptr) {
+ auto js_error = create_js_error("context is null", ctx);
+ return JS_Throw(ctx, js_error);
+ }
+
+ try {
+ auto json_result = context->capacitor_interface->app_api_getInfo();
+ if (json_result.empty()) {
+ return JS_NULL;
+ }
+
+ return JS_ParseJSON(ctx, json_result.c_str(), strlen(json_result.c_str()), "");
+ } catch (std::exception &ex) {
+ auto js_error = create_js_error(ex.what(), ctx);
+ return JS_Throw(ctx, js_error);
+ }
+}
+
+JSValue api_app_get_state(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
+ auto *context = (Context *)JS_GetContextOpaque(ctx);
+ if (context == nullptr) {
+ auto js_error = create_js_error("context is null", ctx);
+ return JS_Throw(ctx, js_error);
+ }
+
+ try {
+ auto json_result = context->capacitor_interface->app_api_getState();
+ if (json_result.empty()) {
+ return JS_NULL;
+ }
+
+ return JS_ParseJSON(ctx, json_result.c_str(), strlen(json_result.c_str()), "");
+ } catch (std::exception &ex) {
+ auto js_error = create_js_error(ex.what(), ctx);
+ return JS_Throw(ctx, js_error);
+ }
+}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.h b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.h
new file mode 100644
index 00000000..338c293c
--- /dev/null
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_app.h
@@ -0,0 +1,9 @@
+#ifndef ANDROID_JS_ENGINE_API_APP_H
+#define ANDROID_JS_ENGINE_API_APP_H
+
+#include "../quickjs/quickjs.h"
+
+JSValue api_app_get_info(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
+JSValue api_app_get_state(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
+
+#endif //ANDROID_JS_ENGINE_API_APP_H
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.cpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.cpp
index b35be925..d12dc571 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.cpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.cpp
@@ -21,6 +21,43 @@ JSValue api_notifications_schedule(JSContext *ctx, JSValueConst this_val, int ar
} catch (std::exception &ex) {
JS_FreeCString(ctx, options_c_str);
+ auto js_error = create_js_error(ex.what(), ctx);
+ return JS_Throw(ctx, js_error);
+ }
+}
+
+JSValue api_notifications_set_badge(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
+ auto *context = (Context *)JS_GetContextOpaque(ctx);
+ if (context == nullptr) {
+ auto js_error = create_js_error("context is null", ctx);
+ return JS_Throw(ctx, js_error);
+ }
+
+ auto options_str = JS_JSONStringify(ctx, argv[0], JS_UNDEFINED, JS_UNDEFINED);
+ const auto *options_c_str = JS_ToCString(ctx, options_str);
+
+ try {
+ context->capacitor_interface->notifications_api_setBadge(options_c_str);
+
+ return JS_UNDEFINED;
+ } catch (std::exception &ex) {
+ auto js_error = create_js_error(ex.what(), ctx);
+ return JS_Throw(ctx, js_error);
+ }
+}
+
+JSValue api_notifications_clear_badge(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
+ auto *context = (Context *)JS_GetContextOpaque(ctx);
+ if (context == nullptr) {
+ auto js_error = create_js_error("context is null", ctx);
+ return JS_Throw(ctx, js_error);
+ }
+
+ try {
+ context->capacitor_interface->notifications_api_clearBadge();
+
+ return JS_UNDEFINED;
+ } catch (std::exception &ex) {
auto js_error = create_js_error(ex.what(), ctx);
return JS_Throw(ctx, js_error);
}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.h b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.h
index 966853b5..dc3c5ec1 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.h
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor-api/api_notifications.h
@@ -4,5 +4,7 @@
#include "../quickjs/quickjs.h"
JSValue api_notifications_schedule(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
+JSValue api_notifications_set_badge(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
+JSValue api_notifications_clear_badge(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
#endif //ANDROID_JS_ENGINE_API_NOTIFICATIONS_H
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor.hpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor.hpp
index bf3fb3ec..1d85b286 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor.hpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/capacitor.hpp
@@ -21,6 +21,11 @@ class CapacitorInterface {
virtual void kv_api_remove(std::string key) = 0;
virtual void notifications_api_schedule(std::string options_json) = 0;
+ virtual void notifications_api_setBadge(std::string options_json) = 0;
+ virtual void notifications_api_clearBadge() = 0;
+
+ virtual std::string app_api_getInfo() = 0;
+ virtual std::string app_api_getState() = 0;
protected:
CapacitorInterface() = default;
};
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.cpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.cpp
index b6239d31..e266db8b 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.cpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.cpp
@@ -1,5 +1,6 @@
#include "context.hpp"
+#include "capacitor-api/api_app.h"
#include "capacitor-api/api_device.h"
#include "capacitor-api/api_geolocation.h"
#include "capacitor-api/api_kv.h"
@@ -304,6 +305,7 @@ void Context::init_capacitor_api() {
this->init_capacitor_geolocation_api();
this->init_capacitor_kv_api();
this->init_capacitor_notifications_api();
+ this->init_capacitor_app_api();
}
void Context::init_capacitor_device_api() const {
@@ -342,7 +344,8 @@ void Context::init_capacitor_notifications_api() const {
notifications = JS_NewObject(this->qjs_context);
JS_SetPropertyStr(this->qjs_context, notifications, "schedule", JS_NewCFunction(this->qjs_context, api_notifications_schedule, "schedule", 1));
-
+ JS_SetPropertyStr(this->qjs_context, notifications, "setBadge", JS_NewCFunction(this->qjs_context, api_notifications_set_badge, "setBadge", 1));
+ JS_SetPropertyStr(this->qjs_context, notifications, "clearBadge", JS_NewCFunction(this->qjs_context, api_notifications_clear_badge, "clearBadge", 0));
JS_SetPropertyStr(this->qjs_context, global_obj, "CapacitorNotifications", notifications);
JS_FreeValue(this->qjs_context, global_obj);
@@ -359,4 +362,18 @@ void Context::init_capacitor_geolocation_api() const {
JS_SetPropertyStr(this->qjs_context, global_obj, "CapacitorGeolocation", geolocation);
JS_FreeValue(this->qjs_context, global_obj);
-}
\ No newline at end of file
+}
+
+void Context::init_capacitor_app_api() const {
+ JSValue global_obj, app;
+
+ global_obj = JS_GetGlobalObject(this->qjs_context);
+ app = JS_NewObject(this->qjs_context);
+
+ JS_SetPropertyStr(this->qjs_context, app, "getState", JS_NewCFunction(this->qjs_context, api_app_get_state, "getState", 0));
+ JS_SetPropertyStr(this->qjs_context, app, "getInfo", JS_NewCFunction(this->qjs_context, api_app_get_info, "getInfo", 0));
+
+ JS_SetPropertyStr(this->qjs_context, global_obj, "CapacitorApp", app);
+
+ JS_FreeValue(this->qjs_context, global_obj);
+}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.hpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.hpp
index 5c513f20..370d9e10 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.hpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/js-engine/src/context.hpp
@@ -55,6 +55,7 @@ class Context {
void init_capacitor_geolocation_api() const;
void init_capacitor_kv_api() const;
void init_capacitor_notifications_api() const;
+ void init_capacitor_app_api() const;
};
#endif // CAPACITOR_BACKGROUND_RUNNER_CONTEXT_H
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.cpp b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.cpp
index d46f6604..d5ecc669 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.cpp
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.cpp
@@ -168,6 +168,47 @@ void NativeCapacitorInterface::notifications_api_schedule(std::string options_js
}
}
+void NativeCapacitorInterface::notifications_api_setBadge(std::string options_json) {
+ auto *env = this->java->getEnv();
+ if (env == nullptr) {
+ throw new NativeInterfaceException("JVM environment is null");
+ }
+
+ auto *notification = env->GetObjectField(this->api, this->java->capacitor_api_notification_field);
+ auto jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ auto *options_j_str = env->NewStringUTF(options_json.c_str());
+
+ env->CallVoidMethod(notification, this->java->capacitor_api_notifications_setBadge_method, options_j_str);
+ jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+}
+
+void NativeCapacitorInterface::notifications_api_clearBadge() {
+ auto *env = this->java->getEnv();
+ if (env == nullptr) {
+ throw new NativeInterfaceException("JVM environment is null");
+ }
+
+ auto *notification = env->GetObjectField(this->api, this->java->capacitor_api_notification_field);
+ auto jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ env->CallVoidMethod(notification, this->java->capacitor_api_notifications_clearBadge_method);
+ jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+}
+
+// Geolocation API
std::string NativeCapacitorInterface::geolocation_api_getCurrentPosition() {
auto *env = this->java->getEnv();
if (env == nullptr) {
@@ -197,3 +238,64 @@ std::string NativeCapacitorInterface::geolocation_api_getCurrentPosition() {
return json;
}
+
+// App API
+std::string NativeCapacitorInterface::app_api_getInfo() {
+ auto *env = this->java->getEnv();
+ if (env == nullptr) {
+ throw new NativeInterfaceException("JVM environment is null");
+ }
+
+ auto *app = env->GetObjectField(this->api, this->java->capacitor_api_app_field);
+ auto jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ auto value_j_str = (jstring)env->CallObjectMethod(app, this->java->capacitor_api_app_getInfo_method);
+ jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ if (value_j_str == nullptr) {
+ return "";
+ }
+
+ const auto *c_json_str = env->GetStringUTFChars(value_j_str, nullptr);
+
+ auto json = std::string(c_json_str);
+ env->ReleaseStringUTFChars(value_j_str, c_json_str);
+
+ return json;
+}
+
+std::string NativeCapacitorInterface::app_api_getState() {
+ auto *env = this->java->getEnv();
+ if (env == nullptr) {
+ throw new NativeInterfaceException("JVM environment is null");
+ }
+
+ auto *app = env->GetObjectField(this->api, this->java->capacitor_api_app_field);
+ auto jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ auto value_j_str = (jstring)env->CallObjectMethod(app, this->java->capacitor_api_app_getState_method);
+ jvm_exception = get_jvm_exception(env);
+ if (jvm_exception != nullptr) {
+ throw *jvm_exception;
+ }
+
+ if (value_j_str == nullptr) {
+ return "";
+ }
+
+ const auto *c_json_str = env->GetStringUTFChars(value_j_str, nullptr);
+
+ auto json = std::string(c_json_str);
+ env->ReleaseStringUTFChars(value_j_str, c_json_str);
+
+ return json;
+}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.h b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.h
index 71e4f92c..228f8927 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.h
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/cpp/native_capacitor_interface.h
@@ -24,6 +24,11 @@ class NativeCapacitorInterface : public CapacitorInterface {
virtual void kv_api_remove(std::string key);
virtual void notifications_api_schedule(std::string options_json);
+ virtual void notifications_api_setBadge(std::string options_json);
+ virtual void notifications_api_clearBadge();
+
+ virtual std::string app_api_getInfo();
+ virtual std::string app_api_getState();
};
#endif //ANDROID_JS_ENGINE_NATIVE_CAPACITOR_INTERFACE_H
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/NativeCapacitorAPI.kt b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/NativeCapacitorAPI.kt
index bf26c7d0..42f5e656 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/NativeCapacitorAPI.kt
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/NativeCapacitorAPI.kt
@@ -1,5 +1,6 @@
package io.ionic.android_js_engine
+import io.ionic.android_js_engine.capacitor_api.AppAPI
import io.ionic.android_js_engine.capacitor_api.DeviceAPI
import io.ionic.android_js_engine.capacitor_api.GeolocationAPI
import io.ionic.android_js_engine.capacitor_api.KVAPI
@@ -10,6 +11,7 @@ class NativeCapacitorAPI {
private var device: DeviceAPI? = null
private var notifications: NotificationsAPI? = null
private var geolocation: GeolocationAPI? = null
+ private var app: AppAPI? = null
fun initNotificationsAPI(api: NotificationsAPI) {
this.notifications = api
@@ -26,4 +28,8 @@ class NativeCapacitorAPI {
fun initKVAPI(api: KVAPI) {
this.kv = api
}
+
+ fun initAppAPI(api: AppAPI) {
+ this.app = api
+ }
}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/AppAPI.kt b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/AppAPI.kt
new file mode 100644
index 00000000..26c8fa54
--- /dev/null
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/AppAPI.kt
@@ -0,0 +1,7 @@
+package io.ionic.android_js_engine.capacitor_api
+
+interface AppAPI {
+ fun getState(): String
+
+ fun getInfo(): String
+}
diff --git a/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/NotificationsAPI.kt b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/NotificationsAPI.kt
index 60b655c0..343ebc7d 100644
--- a/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/NotificationsAPI.kt
+++ b/packages/android-js-engine/AndroidJSEngine/src/main/java/io/ionic/android_js_engine/capacitor_api/NotificationsAPI.kt
@@ -2,4 +2,8 @@ package io.ionic.android_js_engine.capacitor_api
interface NotificationsAPI {
fun schedule(jsonString: String)
+
+ fun setBadge(jsonString: String)
+
+ fun clearBadge()
}
diff --git a/packages/capacitor-plugin/API.md b/packages/capacitor-plugin/API.md
index 13ad7eda..f3d80942 100644
--- a/packages/capacitor-plugin/API.md
+++ b/packages/capacitor-plugin/API.md
@@ -46,9 +46,11 @@ A simple string key / value store backed by UserDefaults on iOS and Shared Prefe
Send basic local notifications.
-| Prop | Type | Description | Since |
-| -------------- | ------------------------------------- | ----------------------------- | ----- |
-| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| Prop | Type | Description | Since |
+| ---------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------- | ----- |
+| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| **`setBadge`** | (options: NotificationBadgeOptions) => void
| Set the application badge count | 2.0.0 |
+| **`clearBadge`** | () => void
| Clears the application badge count | 2.0.0 |
#### NotificationScheduleOptions
@@ -75,6 +77,15 @@ Send basic local notifications.
| **`channelId`** | string
| Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls `setChannelId()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android 26+. | 1.0.0 |
+#### NotificationBadgeOptions
+
+| Prop | Type | Description | Since |
+| -------------------------- | ------------------- | ------------------------------------------------------------------------------------- | ----- |
+| **`count`** | number
| The number to set on the application badge count. | 2.0.0 |
+| **`notificationTitle`** | string
| The **required** title for the associated badge count notification. Only for Android. | 2.0.0 |
+| **`notificationSubtitle`** | string
| The subtitle for the associated badge count notification. Only for Android. | 2.0.0 |
+
+
#### CapacitorGeolocation
Get access to device location information.
@@ -101,7 +112,7 @@ Get access to device location information.
Interact with a watch paired with this app
-sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application.
+sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapacitorWatch Watch application.
They could be used if a native watch app is developed as a companion app to a Capacitor app
| Prop | Type | Description |
@@ -113,4 +124,29 @@ They could be used if a native watch app is developed as a companion app to a Ca
| **`updateWatchUI`** | (options: { watchUI: string; }) => void
| Replaces the current UI on the watch with what is specified here. |
| **`updateWatchData`** | (options: { data: { [key: string]: string; }; }) => void
| Updates the data the watch is using to display variables in text and button fields |
+
+#### CapacitorApp
+
+| Prop | Type |
+| -------------- | ------------------------------------------------------ |
+| **`getState`** | () => AppState
|
+| **`getInfo`** | () => AppInfo
|
+
+
+#### AppState
+
+| Prop | Type | Description | Since |
+| -------------- | -------------------- | --------------------------------- | ----- |
+| **`isActive`** | boolean
| Whether the app is active or not. | 1.0.0 |
+
+
+#### AppInfo
+
+| Prop | Type | Description | Since |
+| ------------- | ------------------- | --------------------------------------------------------------------------------------------------- | ----- |
+| **`name`** | string
| The name of the app. | 1.0.0 |
+| **`id`** | string
| The identifier of the app. On iOS it's the Bundle Identifier. On Android it's the Application ID | 1.0.0 |
+| **`build`** | string
| The build version. On iOS it's the CFBundleVersion. On Android it's the versionCode. | 1.0.0 |
+| **`version`** | string
| The app version. On iOS it's the CFBundleShortVersionString. On Android it's package's versionName. | 1.0.0 |
+
diff --git a/packages/capacitor-plugin/README.md b/packages/capacitor-plugin/README.md
index 50e56cec..9f2e0579 100644
--- a/packages/capacitor-plugin/README.md
+++ b/packages/capacitor-plugin/README.md
@@ -441,9 +441,11 @@ A simple string key / value store backed by UserDefaults on iOS and Shared Prefe
Send basic local notifications.
-| Prop | Type | Description | Since |
-| -------------- | ------------------------------------- | ----------------------------- | ----- |
-| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| Prop | Type | Description | Since |
+| ---------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------- | ----- |
+| **`schedule`** | (options: {}) => void
| Schedule a local notification | 1.0.0 |
+| **`setBadge`** | (options: NotificationBadgeOptions) => void
| Set the application badge count | 2.0.0 |
+| **`clearBadge`** | () => void
| Clears the application badge count | 2.0.0 |
#### NotificationScheduleOptions
@@ -470,6 +472,15 @@ Send basic local notifications.
| **`channelId`** | string
| Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls `setChannelId()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android 26+. | 1.0.0 |
+#### NotificationBadgeOptions
+
+| Prop | Type | Description | Since |
+| -------------------------- | ------------------- | ------------------------------------------------------------------------------------- | ----- |
+| **`count`** | number
| The number to set on the application badge count. | 2.0.0 |
+| **`notificationTitle`** | string
| The **required** title for the associated badge count notification. Only for Android. | 2.0.0 |
+| **`notificationSubtitle`** | string
| The subtitle for the associated badge count notification. Only for Android. | 2.0.0 |
+
+
#### CapacitorGeolocation
Get access to device location information.
@@ -496,7 +507,7 @@ Get access to device location information.
Interact with a watch paired with this app
-sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application.
+sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapacitorWatch Watch application.
They could be used if a native watch app is developed as a companion app to a Capacitor app
| Prop | Type | Description |
@@ -509,4 +520,29 @@ They could be used if a native watch app is developed as a companion app to a Ca
| **`updateWatchData`** | (options: { data: { [key: string]: string; }; }) => void
| Updates the data the watch is using to display variables in text and button fields |
+#### CapacitorApp
+
+| Prop | Type |
+| -------------- | ------------------------------------------------------ |
+| **`getState`** | () => AppState
|
+| **`getInfo`** | () => AppInfo
|
+
+
+#### AppState
+
+| Prop | Type | Description | Since |
+| -------------- | -------------------- | --------------------------------- | ----- |
+| **`isActive`** | boolean
| Whether the app is active or not. | 1.0.0 |
+
+
+#### AppInfo
+
+| Prop | Type | Description | Since |
+| ------------- | ------------------- | --------------------------------------------------------------------------------------------------- | ----- |
+| **`name`** | string
| The name of the app. | 1.0.0 |
+| **`id`** | string
| The identifier of the app. On iOS it's the Bundle Identifier. On Android it's the Application ID | 1.0.0 |
+| **`build`** | string
| The build version. On iOS it's the CFBundleVersion. On Android it's the versionCode. | 1.0.0 |
+| **`version`** | string
| The app version. On iOS it's the CFBundleShortVersionString. On Android it's package's versionName. | 1.0.0 |
+
+
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt
index 20bd35fa..222c6424 100644
--- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunner.kt
@@ -13,6 +13,8 @@ import io.ionic.android_js_engine.Context
import io.ionic.android_js_engine.NativeCapacitorAPI
import io.ionic.android_js_engine.NativeJSFunction
import io.ionic.android_js_engine.Runner
+import io.ionic.backgroundrunner.plugin.api.App
+import io.ionic.backgroundrunner.plugin.api.AppState
import io.ionic.backgroundrunner.plugin.api.Device
import io.ionic.backgroundrunner.plugin.api.Geolocation
import io.ionic.backgroundrunner.plugin.api.KV
@@ -187,6 +189,7 @@ class BackgroundRunner(context: android.content.Context) {
api.initKVAPI(KV(context, config.label))
api.initGeolocationAPI(Geolocation(context))
api.initNotificationsAPI(Notifications(context))
+ api.initAppAPI(App(context))
val newContext = runner.createContext(contextName, api)
newContext.execute(srcFile, false)
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt
index b1701d80..c2876524 100644
--- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/BackgroundRunnerPlugin.kt
@@ -9,6 +9,7 @@ import com.getcapacitor.PluginMethod
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.annotation.Permission
import com.getcapacitor.annotation.PermissionCallback
+import io.ionic.backgroundrunner.plugin.api.AppState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -30,27 +31,36 @@ import kotlinx.coroutines.runBlocking
)
class BackgroundRunnerPlugin: Plugin() {
private var impl: BackgroundRunner? = null
+ private var appState: AppState = AppState.getInstance()
companion object {
const val GEOLOCATION = "geolocation"
const val NOTIFICATIONS = "notifications"
}
+ override fun handleOnStart() {
+ super.handleOnStart()
+ appState.isActive = true
+ }
+
override fun handleOnPause() {
super.handleOnPause()
Log.d("Background Runner", "registering runner workers")
+ appState.isActive = false
impl?.scheduleBackgroundTask(this.context)
}
override fun handleOnStop() {
super.handleOnStop()
Log.d("Background Runner", "shutting down foreground runner")
+ appState.isActive = false
impl?.shutdown()
}
override fun handleOnResume() {
super.handleOnResume()
Log.d("Background Runner", "starting foreground runner")
+ appState.isActive = true
impl?.start()
}
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/App.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/App.kt
new file mode 100644
index 00000000..12423948
--- /dev/null
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/App.kt
@@ -0,0 +1,31 @@
+package io.ionic.backgroundrunner.plugin.api
+
+import androidx.core.content.pm.PackageInfoCompat
+import com.getcapacitor.util.InternalUtils
+import io.ionic.android_js_engine.capacitor_api.AppAPI
+import org.json.JSONObject
+
+class App(private val context: android.content.Context): AppAPI {
+
+ override fun getInfo(): String {
+ val packageInfo = InternalUtils.getPackageInfo(context.packageManager, context.packageName)
+ val appInfo = context.applicationInfo
+
+ val appName = if (appInfo.labelRes == 0) appInfo.nonLocalizedLabel.toString() else context.getString(appInfo.labelRes)
+
+ val info = JSONObject()
+ info.put("name", appName)
+ info.put("id", packageInfo.packageName)
+ info.put("build", PackageInfoCompat.getLongVersionCode(packageInfo).toString())
+ info.put("version", packageInfo.versionName)
+
+ return info.toString()
+ }
+
+ override fun getState(): String {
+
+ val state = JSONObject()
+ state.put("isActive", AppState.getInstance().isActive)
+ return state.toString()
+ }
+}
\ No newline at end of file
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/AppState.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/AppState.kt
new file mode 100644
index 00000000..f5bb7464
--- /dev/null
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/AppState.kt
@@ -0,0 +1,14 @@
+package io.ionic.backgroundrunner.plugin.api
+
+class AppState {
+ public var isActive = false
+
+ companion object {
+ @Volatile
+ private var instance: AppState? = null
+
+ fun getInstance() = instance ?: synchronized(this) {
+ instance ?: AppState().also { instance = it }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt
index 8f96ff87..3210a818 100644
--- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notification.kt
@@ -35,12 +35,15 @@ class Notification(jsonObject: JSONObject) {
ongoing = jsonObject.optBoolean("ongoing", false)
autoCancel = jsonObject.optBoolean("autoCancel", false)
- val scheduleDateString = jsonObject.getString("scheduleAt")
-
- val sdf = SimpleDateFormat(jsDateFormat)
- val parsedDate = sdf.parse(scheduleDateString)
-
- scheduleAt = parsedDate ?: Date()
+ val scheduleDateString = jsonObject.optString("scheduleAt", "")
+ if (scheduleDateString.isNotEmpty()) {
+ val sdf = SimpleDateFormat(jsDateFormat)
+ val parsedDate = sdf.parse(scheduleDateString)
+
+ scheduleAt = parsedDate ?: Date()
+ } else {
+ scheduleAt = Date()
+ }
}
fun smallIcon(context: android.content.Context, defaultIcon: Int): Int {
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt
index 6894c921..9ad2ae31 100644
--- a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/Notifications.kt
@@ -17,6 +17,8 @@ import com.getcapacitor.CapConfig
import com.getcapacitor.plugin.util.AssetUtil
import io.ionic.android_js_engine.capacitor_api.NotificationsAPI
import org.json.JSONArray
+import org.json.JSONObject
+
class Notifications(context: Context) : NotificationsAPI {
private val manager: NotificationManagerCompat
@@ -32,12 +34,40 @@ class Notifications(context: Context) : NotificationsAPI {
this.config = CapConfig.loadDefault(context)
this.createNotificationChannel()
+ this.createBadgeNotificationChannel()
}
companion object {
const val notificationIntentKey = "LocalNotificationId"
const val defaultNotificationChannelID = "default"
+ const val defaultBadgeNotificationChannelID = "badge"
+ }
+
+ override fun clearBadge() {
+ val builder = NotificationCompat.Builder(context, defaultBadgeNotificationChannelID)
+ builder.setSmallIcon(getDefaultSmallIcon())
+ builder.setNumber(0)
+ builder.setAutoCancel(true)
+
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(1001, builder.build())
+ }
+
+ override fun setBadge(jsonString: String) {
+ val obj = JSONObject(jsonString)
+ val options = SetBadgeOptions(obj)
+
+ val builder = NotificationCompat.Builder(context, defaultBadgeNotificationChannelID)
+ builder.setSmallIcon(getDefaultSmallIcon())
+ builder.setContentTitle(options.messageTitle)
+ builder.setContentText(options.messageSubtitle)
+ builder.setNumber(options.count)
+ builder.setAutoCancel(true)
+
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ manager.notify(1001, builder.build())
}
+
override fun schedule(jsonString: String) {
val notifications = mutableListOf()
@@ -96,6 +126,22 @@ class Notifications(context: Context) : NotificationsAPI {
}
}
+ private fun createBadgeNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val name: CharSequence = "Badge"
+ val description = "Badge"
+ val importance = NotificationManager.IMPORTANCE_LOW
+ val channel = NotificationChannel(defaultBadgeNotificationChannelID, name, importance)
+ channel.description = description
+ channel.setShowBadge(true)
+
+ val notificationManager = context.getSystemService(
+ NotificationManager::class.java
+ )
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
private fun getDefaultSoundUrl(context: Context): Uri? {
val soundId = getDefaultSound(context)
return if (soundId != AssetUtil.RESOURCE_ID_ZERO_VALUE) {
diff --git a/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/SetBadgeOptions.kt b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/SetBadgeOptions.kt
new file mode 100644
index 00000000..e78c6f13
--- /dev/null
+++ b/packages/capacitor-plugin/android/src/main/java/io/ionic/backgroundrunner/plugin/api/SetBadgeOptions.kt
@@ -0,0 +1,11 @@
+package io.ionic.backgroundrunner.plugin.api
+
+import org.json.JSONObject
+
+data class SetBadgeOptions(var count: Int, var messageTitle: String, var messageSubtitle: String) {
+ constructor(jsonObject: JSONObject) : this(0, "", "") {
+ this.count = jsonObject.optInt("count", 0)
+ this.messageTitle = jsonObject.optString("notificationTitle", "")
+ this.messageSubtitle = jsonObject.optString("notificationSubtitle", "")
+ }
+}
\ No newline at end of file
diff --git a/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar b/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar
index 17a96fdd..8a764fe1 100644
Binary files a/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar and b/packages/capacitor-plugin/android/src/main/libs/android-js-engine-release.aar differ
diff --git a/packages/capacitor-plugin/ios/Plugin/BackgroundRunner.swift b/packages/capacitor-plugin/ios/Plugin/BackgroundRunner.swift
index 85dfe20e..7fb2fd98 100644
--- a/packages/capacitor-plugin/ios/Plugin/BackgroundRunner.swift
+++ b/packages/capacitor-plugin/ios/Plugin/BackgroundRunner.swift
@@ -6,22 +6,22 @@ import JavaScriptCore
public class BackgroundRunner {
public static let shared = BackgroundRunner()
public var config: RunnerConfig?
-
+
private var runner = Runner()
public init() {
do {
- config = try self.loadRunnerConfig()
+ config = try self.loadRunnerConfig()
} catch {
print("could not initialize BackgroundRunner: \(error)")
}
}
-
+
public func scheduleBackgroundTasks() throws {
guard let config = config else {
throw BackgroundRunnerPluginError.noRunnerConfig
}
-
+
if !config.autoSchedule {
return
}
@@ -58,7 +58,7 @@ public class BackgroundRunner {
}
}
}
-
+
// swiftlint:disable:next cyclomatic_complexity function_body_length
public func execute(config: RunnerConfig, inputArgs: [String: Any]? = nil, callbackId: String? = nil) throws -> [String: Any]? {
do {
@@ -130,7 +130,6 @@ public class BackgroundRunner {
try context.dispatchEvent(event: config.event, details: args)
waitGroup.wait()
-
if let rejection = rejectionErr {
throw EngineError.jsException(details: rejection.message)
diff --git a/packages/capacitor-plugin/ios/Plugin/BackgroundRunnerPlugin.swift b/packages/capacitor-plugin/ios/Plugin/BackgroundRunnerPlugin.swift
index 832a6a39..c116d959 100644
--- a/packages/capacitor-plugin/ios/Plugin/BackgroundRunnerPlugin.swift
+++ b/packages/capacitor-plugin/ios/Plugin/BackgroundRunnerPlugin.swift
@@ -13,7 +13,7 @@ public class BackgroundRunnerPlugin: CAPPlugin {
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
-
+
initWatchConnectivity()
}
@@ -64,7 +64,7 @@ public class BackgroundRunnerPlugin: CAPPlugin {
guard var config = impl.config else {
throw BackgroundRunnerPluginError.noRunnerConfig
}
-
+
config.event = runnerEvent
// swiftlint:disable:next unowned_variable_capture
@@ -116,7 +116,7 @@ public class BackgroundRunnerPlugin: CAPPlugin {
completionHandler(.failure(BackgroundRunnerPluginError.invalidArgument(reason: "event is missing or invalid")))
return
}
-
+
var args: [String: Any] = [:]
eventArgs.forEach { (key: AnyHashable, value: Any) in
diff --git a/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/App.swift b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/App.swift
new file mode 100644
index 00000000..c3e1881f
--- /dev/null
+++ b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/App.swift
@@ -0,0 +1,31 @@
+import Foundation
+import UIKit
+import JavaScriptCore
+
+@objc protocol CapacitorAppExports: JSExport {
+ static func getInfo() -> [String: Any]?
+ static func getState() -> [String: Any]
+}
+
+class CapacitorApp: NSObject, CapacitorAppExports {
+ static func getInfo() -> [String: Any]? {
+ if let info = Bundle.main.infoDictionary {
+ return [
+ "name": info["CFBundleDisplayName"] as? String ?? "",
+ "id": info["CFBundleIdentifier"] as? String ?? "",
+ "build": info["CFBundleVersion"] as? String ?? "",
+ "version": info["CFBundleShortVersionString"] as? String ?? ""
+ ]
+ }
+
+ return nil
+ }
+
+ static func getState() -> [String: Any] {
+ DispatchQueue.main.sync {
+ return [
+ "isActive": UIApplication.shared.applicationState == UIApplication.State.active
+ ]
+ }
+ }
+}
diff --git a/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/KV.swift b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/KV.swift
index 395b3061..a9a2e6d5 100644
--- a/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/KV.swift
+++ b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/KV.swift
@@ -16,7 +16,7 @@ class CapacitorKVStore: NSObject, CapacitorKVStoreExports {
guard let value = UserDefaults.standard.string(forKey: key) else {
return JSValue(nullIn: JSContext.current())
}
-
+
var valueWrapper: [String: String] = [:]
valueWrapper["value"] = value
diff --git a/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/Notifications.swift b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/Notifications.swift
index bf6d0166..8e7e18e4 100644
--- a/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/Notifications.swift
+++ b/packages/capacitor-plugin/ios/Plugin/CapacitorAPI/Notifications.swift
@@ -8,6 +8,18 @@ enum CapacitorNotificationsErrors: Error, Equatable {
case permissionDenied
}
+struct SetBadgeOption {
+ let count: Int
+
+ init(from dict: [String: Any?]) {
+ if let optionsCount = dict["count"] as? Int {
+ count = optionsCount
+ } else {
+ count = 0
+ }
+ }
+}
+
struct NotificationOption {
let id: Int
let title: String
@@ -52,6 +64,8 @@ struct NotificationOption {
@objc protocol CapacitorNotificationsExports: JSExport {
static func schedule(_ options: JSValue)
+ static func setBadge(_ options: JSValue)
+ static func clearBadge()
}
class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
@@ -164,4 +178,50 @@ class CapacitorNotifications: NSObject, CapacitorNotificationsExports {
JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
}
}
+
+ static func setBadge(_ options: JSValue) {
+ do {
+ if CapacitorNotifications.checkPermission() != "granted" {
+ throw CapacitorNotificationsErrors.permissionDenied
+ }
+
+ if options.isUndefined || options.isNull {
+ throw CapacitorNotificationsErrors.invalidOptions(reason: "options are null")
+ }
+
+ guard let jsonDict = options.toDictionary() as? [String: Any?] else {
+ throw CapacitorNotificationsErrors.invalidOptions(reason: "options must be an valid object")
+ }
+
+ let badgeOptions = SetBadgeOption(from: jsonDict)
+
+ DispatchQueue.main.sync {
+ if #available(iOS 16.0, *) {
+ UNUserNotificationCenter.current().setBadgeCount(badgeOptions.count)
+ } else {
+ UIApplication.shared.applicationIconBadgeNumber = badgeOptions.count
+ }
+ }
+ } catch {
+ JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
+ }
+ }
+
+ static func clearBadge() {
+ do {
+ if CapacitorNotifications.checkPermission() != "granted" {
+ throw CapacitorNotificationsErrors.permissionDenied
+ }
+
+ DispatchQueue.main.sync {
+ if #available(iOS 16.0, *) {
+ UNUserNotificationCenter.current().setBadgeCount(0)
+ } else {
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ }
+ }
+ } catch {
+ JSContext.current().exception = JSValue(newErrorFromMessage: "\(error)", in: JSContext.current())
+ }
+ }
}
diff --git a/packages/capacitor-plugin/ios/Plugin/Context+CapacitorAPI.swift b/packages/capacitor-plugin/ios/Plugin/Context+CapacitorAPI.swift
index ff837879..0c4e9911 100644
--- a/packages/capacitor-plugin/ios/Plugin/Context+CapacitorAPI.swift
+++ b/packages/capacitor-plugin/ios/Plugin/Context+CapacitorAPI.swift
@@ -7,5 +7,6 @@ extension Context {
jsContext.setObject(CapacitorWatch(), forKeyedSubscript: "CapacitorWatch" as NSString)
jsContext.setObject(CapacitorNotifications.self, forKeyedSubscript: "CapacitorNotifications" as NSString)
jsContext.setObject(CapacitorDevice(), forKeyedSubscript: "CapacitorDevice" as NSString)
+ jsContext.setObject(CapacitorApp.self, forKeyedSubscript: "CapacitorApp" as NSString)
}
}
diff --git a/packages/capacitor-plugin/src/apis.ts b/packages/capacitor-plugin/src/apis.ts
index b248e4f6..132eabb7 100644
--- a/packages/capacitor-plugin/src/apis.ts
+++ b/packages/capacitor-plugin/src/apis.ts
@@ -55,6 +55,31 @@ export interface NetworkStatus {
connectionType: string;
}
+export interface NotificationBadgeOptions {
+ /**
+ * The number to set on the application badge count.
+ *
+ * @since 2.0.0
+ */
+ count: number;
+ /**
+ * The **required** title for the associated badge count notification.
+ *
+ * Only for Android.
+ *
+ * @since 2.0.0
+ */
+ notificationTitle: string;
+ /**
+ * The subtitle for the associated badge count notification.
+ *
+ * Only for Android.
+ *
+ * @since 2.0.0
+ */
+ notificationSubtitle: string;
+}
+
export interface NotificationScheduleOptions {
/**
* The notification identifier.
@@ -243,6 +268,51 @@ export interface NotificationScheduleOptions {
channelId?: string;
}
+export interface AppInfo {
+ /**
+ * The name of the app.
+ *
+ * @since 1.0.0
+ */
+ name: string;
+
+ /**
+ * The identifier of the app.
+ * On iOS it's the Bundle Identifier.
+ * On Android it's the Application ID
+ *
+ * @since 1.0.0
+ */
+ id: string;
+
+ /**
+ * The build version.
+ * On iOS it's the CFBundleVersion.
+ * On Android it's the versionCode.
+ *
+ * @since 1.0.0
+ */
+ build: string;
+
+ /**
+ * The app version.
+ * On iOS it's the CFBundleShortVersionString.
+ * On Android it's package's versionName.
+ *
+ * @since 1.0.0
+ */
+ version: string;
+}
+
+export interface AppState {
+ /**
+ * Whether the app is active or not.
+ *
+ * @since 1.0.0
+ */
+ isActive: boolean;
+}
+
/**
* Get access to device location information.
*
@@ -315,12 +385,27 @@ export interface CapacitorNotifications {
* @since 1.0.0
*/
schedule: (options: NotificationScheduleOptions[]) => void;
+ /**
+ * Set the application badge count
+ * @example `CapacitorNotifications.setBadge({...})`
+ * @param options
+ * @returns void
+ * @since 2.0.0
+ */
+ setBadge: (options: NotificationBadgeOptions) => void;
+ /**
+ * Clears the application badge count
+ * @example `CapacitorNotifications.clearBadge()`
+ * @returns void
+ * @since 2.0.0
+ */
+ clearBadge: () => void;
}
/**
* Interact with a watch paired with this app
*
- * sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapactiorWatch Watch application.
+ * sendMessage, transferUserInfo and updateApplicationContext are raw routes to the WCSession delegate methods, but have no effects currently in a CapacitorWatch Watch application.
* They could be used if a native watch app is developed as a companion app to a Capacitor app
*/
export interface CapacitorWatch {
@@ -360,10 +445,16 @@ export interface CapacitorWatch {
updateWatchData: (options: { data: { [key: string]: string } }) => void;
}
+export interface CapacitorApp {
+ getState: () => AppState;
+ getInfo: () => AppInfo;
+}
+
export interface CapacitorAPI {
CapacitorDevice: CapacitorDevice;
CapacitorKV: CapacitorKV;
CapacitorNotifications: CapacitorNotifications;
CapacitorGeolocation: CapacitorGeolocation;
CapacitorWatch: CapacitorWatch;
+ CapacitorApp: CapacitorApp;
}