mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Android: Fixes non-working alarms
Also imported react-native-alarm-notificatio into the project
This commit is contained in:
parent
c9831833c4
commit
138bc8144b
@ -13,7 +13,8 @@
|
||||
"@joplin/turndown",
|
||||
"@joplin/turndown-plugin-gfm",
|
||||
"@joplin/tools",
|
||||
"@joplin/react-native-saf-x"
|
||||
"@joplin/react-native-saf-x",
|
||||
"@joplin/react-native-alarm-notification"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -317,6 +317,9 @@
|
||||
"packages/app-tools/github_oauth_token.txt": true,
|
||||
"packages/generator-joplin/generators/app/templates/api/": true,
|
||||
"packages/htmlpack/dist/": true,
|
||||
"packages/react-native-alarm-notification/android/build": true,
|
||||
"packages/react-native-saf-x/android/build": true,
|
||||
"packages/react-native-saf-x/android/wrapper": true,
|
||||
"packages/renderer/**/.vscode/": true,
|
||||
"packages/renderer/**/copyLib.bat": true,
|
||||
"packages/renderer/**/node_modules/": true,
|
||||
|
@ -306,7 +306,7 @@ PODS:
|
||||
- React-jsinspector (0.70.6)
|
||||
- React-logger (0.70.6):
|
||||
- glog
|
||||
- react-native-alarm-notification (1.0.7):
|
||||
- react-native-alarm-notification (2.10.0):
|
||||
- React
|
||||
- react-native-camera (4.2.1):
|
||||
- React-Core
|
||||
@ -490,7 +490,7 @@ DEPENDENCIES:
|
||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- react-native-alarm-notification (from `../node_modules/joplin-rn-alarm-notification`)
|
||||
- "react-native-alarm-notification (from `../node_modules/@joplin/react-native-alarm-notification`)"
|
||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
|
||||
@ -597,7 +597,7 @@ EXTERNAL SOURCES:
|
||||
React-logger:
|
||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||
react-native-alarm-notification:
|
||||
:path: "../node_modules/joplin-rn-alarm-notification"
|
||||
:path: "../node_modules/@joplin/react-native-alarm-notification"
|
||||
react-native-camera:
|
||||
:path: "../node_modules/react-native-camera"
|
||||
react-native-document-picker:
|
||||
@ -714,7 +714,7 @@ SPEC CHECKSUMS:
|
||||
React-jsiexecutor: b4a65947391c658450151275aa406f2b8263178f
|
||||
React-jsinspector: 60769e5a0a6d4b32294a2456077f59d0266f9a8b
|
||||
React-logger: 1623c216abaa88974afce404dc8f479406bbc3a0
|
||||
react-native-alarm-notification: 4e150e89c1707e057bc5e8c87ab005f1ea4b8d52
|
||||
react-native-alarm-notification: 2218b44c1207344a90e584709f13c7b324073bf4
|
||||
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
|
||||
react-native-document-picker: 958e2bc82e128be69055be261aeac8d872c8d34c
|
||||
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
||||
|
@ -16,6 +16,21 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const localPackages = {
|
||||
'@joplin/lib': path.resolve(__dirname, '../lib/'),
|
||||
'@joplin/renderer': path.resolve(__dirname, '../renderer/'),
|
||||
'@joplin/tools': path.resolve(__dirname, '../tools/'),
|
||||
'@joplin/fork-htmlparser2': path.resolve(__dirname, '../fork-htmlparser2/'),
|
||||
'@joplin/fork-uslug': path.resolve(__dirname, '../fork-uslug/'),
|
||||
'@joplin/react-native-saf-x': path.resolve(__dirname, '../react-native-saf-x/'),
|
||||
'@joplin/react-native-alarm-notification': path.resolve(__dirname, '../react-native-alarm-notification/'),
|
||||
};
|
||||
|
||||
const watchedFolders = [];
|
||||
for (const [, v] of Object.entries(localPackages)) {
|
||||
watchedFolders.push(v);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
transformer: {
|
||||
getTransformOptions: async () => ({
|
||||
@ -26,26 +41,19 @@ module.exports = {
|
||||
}),
|
||||
},
|
||||
resolver: {
|
||||
// This configuration allows you to build React-Native modules and
|
||||
// * test them without having to publish the module. Any exports provided
|
||||
// * by your source should be added to the "target" parameter. Any import
|
||||
// * not matched by a key in target will have to be located in the embedded
|
||||
// * app's node_modules directory.
|
||||
// This configuration allows you to build React-Native modules and test
|
||||
// them without having to publish the module. Any exports provided by
|
||||
// your source should be added to the "target" parameter. Any import not
|
||||
// matched by a key in target will have to be located in the embedded
|
||||
// app's node_modules directory.
|
||||
//
|
||||
extraNodeModules: new Proxy(
|
||||
// The first argument to the Proxy constructor is passed as
|
||||
// * "target" to the "get" method below.
|
||||
// * Put the names of the libraries included in your reusable
|
||||
// * module as they would be imported when the module is actually used.
|
||||
// The first argument to the Proxy constructor is passed as "target"
|
||||
// to the "get" method below. Put the names of the libraries
|
||||
// included in your reusable module as they would be imported when
|
||||
// the module is actually used.
|
||||
//
|
||||
{
|
||||
'@joplin/lib': path.resolve(__dirname, '../lib/'),
|
||||
'@joplin/renderer': path.resolve(__dirname, '../renderer/'),
|
||||
'@joplin/tools': path.resolve(__dirname, '../tools/'),
|
||||
'@joplin/fork-htmlparser2': path.resolve(__dirname, '../fork-htmlparser2/'),
|
||||
'@joplin/fork-uslug': path.resolve(__dirname, '../fork-uslug/'),
|
||||
'@joplin/react-native-saf-x': path.resolve(__dirname, '../react-native-saf-x/'),
|
||||
},
|
||||
localPackages,
|
||||
{
|
||||
get: (target, name) => {
|
||||
if (target.hasOwnProperty(name)) {
|
||||
@ -57,12 +65,5 @@ module.exports = {
|
||||
),
|
||||
},
|
||||
projectRoot: path.resolve(__dirname),
|
||||
watchFolders: [
|
||||
path.resolve(__dirname, '../lib'),
|
||||
path.resolve(__dirname, '../renderer'),
|
||||
path.resolve(__dirname, '../tools'),
|
||||
path.resolve(__dirname, '../fork-htmlparser2'),
|
||||
path.resolve(__dirname, '../fork-uslug'),
|
||||
path.resolve(__dirname, '../react-native-saf-x'),
|
||||
],
|
||||
watchFolders: watchedFolders,
|
||||
};
|
||||
|
@ -19,6 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.10",
|
||||
"@joplin/react-native-alarm-notification": "~2.10",
|
||||
"@joplin/react-native-saf-x": "~2.10",
|
||||
"@joplin/renderer": "~2.10",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
@ -32,7 +33,6 @@
|
||||
"constants-browserify": "1.0.0",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"events": "3.3.0",
|
||||
"joplin-rn-alarm-notification": "1.0.7",
|
||||
"jsc-android": "241213.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"md5": "2.3.0",
|
||||
|
@ -1,13 +1,13 @@
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import { Notification } from '@joplin/lib/models/Alarm';
|
||||
|
||||
const ReactNativeAN = require('joplin-rn-alarm-notification').default;
|
||||
const ReactNativeAN = require('@joplin/react-native-alarm-notification').default;
|
||||
|
||||
export default class AlarmServiceDriver {
|
||||
|
||||
private logger_: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
public constructor(logger: Logger) {
|
||||
this.logger_ = logger;
|
||||
}
|
||||
|
||||
|
1
packages/react-native-alarm-notification/.gitattributes
vendored
Normal file
1
packages/react-native-alarm-notification/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.pbxproj -text
|
44
packages/react-native-alarm-notification/.gitignore
vendored
Normal file
44
packages/react-native-alarm-notification/.gitignore
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
**/build/
|
||||
**/.idea
|
||||
**/.gradle/
|
||||
**/gradle/
|
||||
gradle*
|
||||
local.properties
|
||||
*.iml
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
*.keystore
|
21
packages/react-native-alarm-notification/LICENSE
Normal file
21
packages/react-native-alarm-notification/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Chukwuemeka Ihedoro
|
||||
|
||||
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.
|
5
packages/react-native-alarm-notification/README.md
Normal file
5
packages/react-native-alarm-notification/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# React Native Alarm Notification
|
||||
|
||||
This is a fork of [https://github.com/emekalites/react-native-alarm-notification](https://github.com/emekalites/react-native-alarm-notification) with a few bugfixes/improvements for Android.
|
||||
|
||||
It's made specifically for [Joplin](https://github.com/laurent22) and while all basic features should work (set/dismiss alarm, set text/icon, etc) it's not fully compatible with the orignal package.
|
145
packages/react-native-alarm-notification/android/build.gradle
Normal file
145
packages/react-native-alarm-notification/android/build.gradle
Normal file
@ -0,0 +1,145 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
// android/build.gradle
|
||||
|
||||
// based on:
|
||||
//
|
||||
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle
|
||||
// original location:
|
||||
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle
|
||||
//
|
||||
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
|
||||
// original location:
|
||||
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
|
||||
|
||||
def DEFAULT_COMPILE_SDK_VERSION = 31
|
||||
def DEFAULT_BUILD_TOOLS_VERSION = '31.0.0'
|
||||
def DEFAULT_MIN_SDK_VERSION = 21
|
||||
def DEFAULT_TARGET_SDK_VERSION = 31
|
||||
|
||||
def safeExtGet(prop, fallback) {
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
buildscript {
|
||||
// The Android Gradle plugin is only required when opening the android folder stand-alone.
|
||||
// This avoids unnecessary downloads and potential conflicts when the library is included as a
|
||||
// module dependency in an application project.
|
||||
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
|
||||
if (project == rootProject) {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
|
||||
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
|
||||
targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
// ref: https://www.baeldung.com/maven-local-repository
|
||||
mavenLocal()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url "$rootDir/../node_modules/react-native/android"
|
||||
}
|
||||
maven {
|
||||
// Android JSC is installed from npm
|
||||
url "$rootDir/../node_modules/jsc-android/dist"
|
||||
}
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'com.facebook.react:react-native:+' // From node_modules
|
||||
implementation 'com.google.code.gson:gson:2.8.8'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.6'
|
||||
}
|
||||
|
||||
def configureReactNativePom(def pom) {
|
||||
def packageJson = new JsonSlurper().parseText(file('../package.json').text)
|
||||
|
||||
pom.project {
|
||||
name packageJson.title
|
||||
artifactId packageJson.name
|
||||
version = packageJson.version
|
||||
group = "com.emekalites.react.alarm.notification"
|
||||
description packageJson.description
|
||||
url packageJson.repository.baseUrl
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name packageJson.license
|
||||
url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate { project ->
|
||||
// some Gradle build hooks ref:
|
||||
// https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html
|
||||
task androidJavadoc(type: Javadoc) {
|
||||
source = android.sourceSets.main.java.srcDirs
|
||||
classpath += files(android.bootClasspath)
|
||||
// Must be removed due to error "Configuration with name 'compile' not found."
|
||||
// classpath += files(project.getConfigurations().getByName('compile').asList())
|
||||
include '**/*.java'
|
||||
}
|
||||
|
||||
task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
|
||||
classifier = 'javadoc'
|
||||
from androidJavadoc.destinationDir
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
include '**/*.java'
|
||||
}
|
||||
|
||||
android.libraryVariants.all { variant ->
|
||||
def name = variant.name.capitalize()
|
||||
def javaCompileTask = variant.javaCompileProvider.get()
|
||||
|
||||
task "jar${name}"(type: Jar, dependsOn: javaCompileTask) {
|
||||
from javaCompileTask.destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives androidSourcesJar
|
||||
archives androidJavadocJar
|
||||
}
|
||||
|
||||
task installArchives(type: Upload) {
|
||||
configuration = configurations.archives
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.emekalites.react.alarm.notification">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application>
|
||||
<receiver
|
||||
android:name="com.emekalites.react.alarm.notification.AlarmReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="ACTION_DISMISS" />
|
||||
<action android:name="ACTION_SNOOZE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="com.emekalites.react.alarm.notification.AlarmDismissReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<receiver
|
||||
android:name="com.emekalites.react.alarm.notification.AlarmBootReceiver"
|
||||
android:directBootAware="true"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,198 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ActivityEventListener;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ANModule extends ReactContextBaseJavaModule implements ActivityEventListener {
|
||||
|
||||
private static final String E_SCHEDULE_ALARM_FAILED = "E_SCHEDULE_ALARM_FAILED";
|
||||
|
||||
private static ReactApplicationContext mReactContext;
|
||||
|
||||
private final AlarmUtil alarmUtil;
|
||||
private final AlarmModelCodec codec = new AlarmModelCodec();
|
||||
|
||||
ANModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mReactContext = reactContext;
|
||||
alarmUtil = new AlarmUtil((Application) reactContext.getApplicationContext());
|
||||
reactContext.addActivityEventListener(this);
|
||||
}
|
||||
|
||||
static ReactApplicationContext getReactAppContext() {
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RNAlarmNotification";
|
||||
}
|
||||
|
||||
// Required for rn built in EventEmitter Calls.
|
||||
@ReactMethod
|
||||
public void addListener(String eventName) {
|
||||
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeListeners(Integer count) {
|
||||
|
||||
}
|
||||
|
||||
private AlarmDatabase getAlarmDB() {
|
||||
return new AlarmDatabase(mReactContext);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void scheduleAlarm(ReadableMap details, Promise promise) {
|
||||
try {
|
||||
Bundle bundle = Arguments.toBundle(details);
|
||||
AlarmModel alarm = AlarmModel.fromBundle(bundle);
|
||||
|
||||
// check if alarm has been set at this time
|
||||
boolean containAlarm = alarmUtil.checkAlarm(getAlarmDB().getAlarmList(1), alarm);
|
||||
if (!containAlarm) {
|
||||
int id = getAlarmDB().insert(alarm);
|
||||
alarm.setId(id);
|
||||
|
||||
alarmUtil.setAlarm(alarm);
|
||||
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putInt("id", id);
|
||||
|
||||
promise.resolve(map);
|
||||
} else {
|
||||
promise.reject(E_SCHEDULE_ALARM_FAILED, "duplicate alarm set at date");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not schedule alarm", e);
|
||||
promise.reject(E_SCHEDULE_ALARM_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void deleteAlarm(int alarmID) {
|
||||
alarmUtil.deleteAlarm(alarmID);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void deleteRepeatingAlarm(int alarmID) {
|
||||
alarmUtil.deleteRepeatingAlarm(alarmID);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendNotification(ReadableMap details) {
|
||||
try {
|
||||
Bundle bundle = Arguments.toBundle(details);
|
||||
AlarmModel alarm = AlarmModel.fromBundle(bundle);
|
||||
|
||||
int id = getAlarmDB().insert(alarm);
|
||||
alarm.setId(id);
|
||||
|
||||
alarmUtil.sendNotification(alarm);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not send notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeFiredNotification(int id) {
|
||||
alarmUtil.removeFiredNotification(id);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeAllFiredNotifications() {
|
||||
alarmUtil.removeAllFiredNotifications();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getScheduledAlarms(Promise promise) throws JSONException {
|
||||
ArrayList<AlarmModel> alarms = alarmUtil.getAlarms();
|
||||
WritableArray array = Arguments.createArray();
|
||||
for (AlarmModel alarm : alarms) {
|
||||
// TODO triple conversion alarm -> string -> json -> map
|
||||
// this is ugly but I don't have time to fix it now
|
||||
WritableMap alarmMap = alarmUtil.convertJsonToMap(new JSONObject(codec.toJson(alarm)));
|
||||
array.pushMap(alarmMap);
|
||||
}
|
||||
promise.resolve(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (Constants.NOTIFICATION_ACTION_CLICK.equals(intent.getAction())) {
|
||||
Bundle bundle = intent.getExtras();
|
||||
try {
|
||||
if (bundle != null) {
|
||||
int alarmId = bundle.getInt(Constants.NOTIFICATION_ID);
|
||||
alarmUtil.removeFiredNotification(alarmId);
|
||||
alarmUtil.doCancelAlarm(alarmId);
|
||||
|
||||
WritableMap response = Arguments.fromBundle(bundle.getBundle("data"));
|
||||
mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit("OnNotificationOpened", response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Couldn't convert bundle to JSON", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getAlarmInfo(Promise promise) {
|
||||
if (getCurrentActivity() == null) {
|
||||
promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = getCurrentActivity().getIntent();
|
||||
if (intent != null) {
|
||||
if (Constants.NOTIFICATION_ACTION_CLICK.equals(intent.getAction()) &&
|
||||
intent.getExtras() != null) {
|
||||
Bundle bundle = intent.getExtras();
|
||||
WritableMap response = Arguments.fromBundle(bundle.getBundle("data"));
|
||||
promise.resolve(response);
|
||||
|
||||
// cleanup
|
||||
|
||||
// other libs may not expect the intent to be null so set an empty intent here
|
||||
getCurrentActivity().setIntent(new Intent());
|
||||
|
||||
int alarmId = bundle.getInt(Constants.NOTIFICATION_ID);
|
||||
alarmUtil.removeFiredNotification(alarmId);
|
||||
alarmUtil.doCancelAlarm(alarmId);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ANPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
||||
return Collections.<NativeModule>singletonList(new ANModule(reactContext));
|
||||
}
|
||||
|
||||
// Deprecated RN 0.47
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AlarmBootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) ||
|
||||
"android.intent.action.QUICKBOOT_POWERON".equals(intent.getAction()) ||
|
||||
"android.intent.action.LOCKED_BOOT_COMPLETED".equals(intent.getAction()) ||
|
||||
"com.htc.intent.action.QUICKBOOT_POWERON".equals(intent.getAction())) {
|
||||
|
||||
Log.i(Constants.TAG, "Rescheduling after boot, intent=" + intent);
|
||||
|
||||
try (AlarmDatabase alarmDB = new AlarmDatabase(context)) {
|
||||
ArrayList<AlarmModel> alarms = alarmDB.getAlarmList(1);
|
||||
AlarmUtil alarmUtil = new AlarmUtil((Application) context.getApplicationContext());
|
||||
|
||||
for (AlarmModel alarm : alarms) {
|
||||
alarmUtil.setAlarm(alarm);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not reschedule alarms on boot", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AlarmDatabase extends SQLiteOpenHelper implements AutoCloseable {
|
||||
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "rnandb";
|
||||
|
||||
private static final String TABLE_NAME = "alarmtbl";
|
||||
|
||||
private static final String COL_ID = "id";
|
||||
private static final String COL_DATA = "gson_data";
|
||||
private static final String COL_ACTIVE = "active";
|
||||
|
||||
private final String CREATE_TABLE_ALARM = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ COL_DATA + " TEXT, "
|
||||
+ COL_ACTIVE + " INTEGER) ";
|
||||
|
||||
private final AlarmModelCodec codec = new AlarmModelCodec();
|
||||
|
||||
AlarmDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_ALARM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL(String.format(" DROP TABLE IF EXISTS %s", CREATE_TABLE_ALARM));
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
AlarmModel getAlarm(int _id) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
AlarmModel alarm = null;
|
||||
|
||||
String selectQuery = "SELECT * FROM " + TABLE_NAME + " WHERE " + COL_ID + " = " + _id;
|
||||
|
||||
try (Cursor cursor = db.rawQuery(selectQuery, null)) {
|
||||
cursor.moveToFirst();
|
||||
|
||||
int id = cursor.getInt(0);
|
||||
String data = cursor.getString(1);
|
||||
int active = cursor.getInt(2);
|
||||
|
||||
Log.d(Constants.TAG, "get alarm -> id:" + id + ", active:" + active + ", " + data);
|
||||
|
||||
|
||||
alarm = codec.fromJson(data);
|
||||
alarm.setId(id);
|
||||
alarm.setActive(active);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "getAlarm: exception", e);
|
||||
}
|
||||
|
||||
return alarm;
|
||||
}
|
||||
|
||||
int insert(AlarmModel alarm) {
|
||||
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
String data = codec.toJson(alarm);
|
||||
Log.i(Constants.TAG, "insert alarm: " + data);
|
||||
|
||||
values.put(COL_DATA, data);
|
||||
values.put(COL_ACTIVE, alarm.getActive());
|
||||
|
||||
return (int) db.insert(TABLE_NAME, null, values);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Error inserting into DB", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void update(AlarmModel alarm) {
|
||||
String where = COL_ID + " = " + alarm.getId();
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
String data = codec.toJson(alarm);
|
||||
Log.d(Constants.TAG, "update alarm: " + data);
|
||||
|
||||
values.put(COL_ID, alarm.getId());
|
||||
values.put(COL_DATA, data);
|
||||
values.put(COL_ACTIVE, alarm.getActive());
|
||||
|
||||
db.update(TABLE_NAME, values, where, null);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Error updating alarm " + alarm, e);
|
||||
}
|
||||
}
|
||||
|
||||
void delete(int id) {
|
||||
String where = COL_ID + "=" + id;
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
db.delete(TABLE_NAME, where, null);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Error deleting alarm with id " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<AlarmModel> getAlarmList(int isActive) {
|
||||
String selectQuery = "SELECT * FROM " + TABLE_NAME;
|
||||
|
||||
if (isActive == 1) {
|
||||
selectQuery += " WHERE " + COL_ACTIVE + " = " + isActive;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ArrayList<AlarmModel> alarms = new ArrayList<>();
|
||||
|
||||
try (Cursor cursor = db.rawQuery(selectQuery, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
int id = cursor.getInt(0);
|
||||
String data = cursor.getString(1);
|
||||
int active = cursor.getInt(2);
|
||||
|
||||
Log.d(Constants.TAG, "get alarm -> id:" + id + ", active:" + active + ", " + data);
|
||||
|
||||
AlarmModel alarm = codec.fromJson(data);
|
||||
alarm.setId(id);
|
||||
alarm.setActive(active);
|
||||
|
||||
alarms.add(alarm);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "getAlarmList: exception cause " + e.getCause() + " message " + e.getMessage());
|
||||
}
|
||||
|
||||
return alarms;
|
||||
}
|
||||
|
||||
ArrayList<AlarmModel> getAlarmList() {
|
||||
return getAlarmList(0);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
public class AlarmDismissReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
AlarmUtil alarmUtil = new AlarmUtil((Application) context.getApplicationContext());
|
||||
try {
|
||||
int notificationId = intent.getExtras().getInt(Constants.NOTIFICATION_ID);
|
||||
if (ANModule.getReactAppContext() != null) {
|
||||
// TODO also send all user-provided args back
|
||||
ANModule.getReactAppContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit("OnNotificationDismissed", "{\"id\": \"" + notificationId + "\"}");
|
||||
}
|
||||
alarmUtil.removeFiredNotification(notificationId);
|
||||
alarmUtil.doCancelAlarm(notificationId);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception when handling notification dismiss. " + e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,444 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AlarmModel implements Serializable {
|
||||
private int id;
|
||||
|
||||
private int minute;
|
||||
private int hour;
|
||||
private int second;
|
||||
|
||||
private int day;
|
||||
private int month;
|
||||
private int year;
|
||||
|
||||
private int alarmId;
|
||||
private String title;
|
||||
private String message;
|
||||
private String channel;
|
||||
private String ticker;
|
||||
private boolean autoCancel;
|
||||
private boolean vibrate;
|
||||
private int vibration;
|
||||
private String smallIcon;
|
||||
private String largeIcon;
|
||||
private boolean playSound;
|
||||
private String soundName;
|
||||
private String soundNames; // separate sounds with comma eg (sound1.mp3,sound2.mp3)
|
||||
private String color;
|
||||
private String scheduleType;
|
||||
private String interval; // hourly, daily, weekly
|
||||
private int intervalValue;
|
||||
private int snoozeInterval; // in minutes
|
||||
private String tag;
|
||||
private Bundle data;
|
||||
private boolean loopSound;
|
||||
private boolean useBigText;
|
||||
private boolean hasButton;
|
||||
private double volume;
|
||||
private boolean bypassDnd;
|
||||
|
||||
private int active = 1; // 1 = yes, 0 = no
|
||||
|
||||
private AlarmModel() {}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setSecond(int second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public void setDay(int day) {
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
public int getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public void setMonth(int month) {
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(int year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public int getAlarmId() {
|
||||
return alarmId;
|
||||
}
|
||||
|
||||
public void setAlarmId(int alarmId) {
|
||||
this.alarmId = alarmId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public String getTicker() {
|
||||
return ticker;
|
||||
}
|
||||
|
||||
public void setTicker(String ticker) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
public boolean isAutoCancel() {
|
||||
return autoCancel;
|
||||
}
|
||||
|
||||
public void setAutoCancel(boolean autoCancel) {
|
||||
this.autoCancel = autoCancel;
|
||||
}
|
||||
|
||||
public boolean isVibrate() {
|
||||
return vibrate;
|
||||
}
|
||||
|
||||
public void setVibrate(boolean vibrate) {
|
||||
this.vibrate = vibrate;
|
||||
}
|
||||
|
||||
public int getVibration() {
|
||||
return vibration;
|
||||
}
|
||||
|
||||
public void setVibration(int vibration) {
|
||||
this.vibration = vibration;
|
||||
}
|
||||
|
||||
public String getSmallIcon() {
|
||||
return smallIcon;
|
||||
}
|
||||
|
||||
public void setSmallIcon(String smallIcon) {
|
||||
this.smallIcon = smallIcon;
|
||||
}
|
||||
|
||||
public String getLargeIcon() {
|
||||
return largeIcon;
|
||||
}
|
||||
|
||||
public void setLargeIcon(String largeIcon) {
|
||||
this.largeIcon = largeIcon;
|
||||
}
|
||||
|
||||
public boolean isPlaySound() {
|
||||
return playSound;
|
||||
}
|
||||
|
||||
public void setPlaySound(boolean playSound) {
|
||||
this.playSound = playSound;
|
||||
}
|
||||
|
||||
public String getSoundName() {
|
||||
return soundName;
|
||||
}
|
||||
|
||||
public void setSoundName(String soundName) {
|
||||
this.soundName = soundName;
|
||||
}
|
||||
|
||||
public String getSoundNames() {
|
||||
return soundNames;
|
||||
}
|
||||
|
||||
public void setSoundNames(String soundNames) {
|
||||
this.soundNames = soundNames;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(String color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getScheduleType() {
|
||||
return scheduleType;
|
||||
}
|
||||
|
||||
public void setScheduleType(String scheduleType) {
|
||||
this.scheduleType = scheduleType;
|
||||
}
|
||||
|
||||
public String getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(String interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
public int getIntervalValue() {
|
||||
return intervalValue;
|
||||
}
|
||||
|
||||
public void setIntervalValue(int intervalValue) {
|
||||
this.intervalValue = intervalValue;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public Bundle getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Bundle data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(int active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public int getSnoozeInterval() {
|
||||
return snoozeInterval;
|
||||
}
|
||||
|
||||
public void setSnoozeInterval(int snoozeInterval) {
|
||||
this.snoozeInterval = snoozeInterval;
|
||||
}
|
||||
|
||||
public boolean isLoopSound() {
|
||||
return loopSound;
|
||||
}
|
||||
|
||||
public void setLoopSound(boolean loopSound) {
|
||||
this.loopSound = loopSound;
|
||||
}
|
||||
|
||||
public boolean isUseBigText() {
|
||||
return useBigText;
|
||||
}
|
||||
|
||||
public void setUseBigText(boolean useBigText) {
|
||||
this.useBigText = useBigText;
|
||||
}
|
||||
|
||||
public boolean isHasButton() {
|
||||
return hasButton;
|
||||
}
|
||||
|
||||
public void setHasButton(boolean hasButton) {
|
||||
this.hasButton = hasButton;
|
||||
}
|
||||
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(double volume) {
|
||||
if (volume > 1 || volume < 0) {
|
||||
this.volume = 0.5;
|
||||
} else {
|
||||
this.volume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBypassDnd() {
|
||||
return bypassDnd;
|
||||
}
|
||||
|
||||
public void setBypassDnd(boolean bypassDnd) {
|
||||
this.bypassDnd = bypassDnd;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AlarmModel{" +
|
||||
"id=" + id +
|
||||
", second=" + second +
|
||||
", minute=" + minute +
|
||||
", hour=" + hour +
|
||||
", day=" + day +
|
||||
", month=" + month +
|
||||
", year=" + year +
|
||||
", alarmId=" + alarmId +
|
||||
", title='" + title + '\'' +
|
||||
", message='" + message + '\'' +
|
||||
", channel='" + channel + '\'' +
|
||||
", ticker='" + ticker + '\'' +
|
||||
", autoCancel=" + autoCancel +
|
||||
", vibrate=" + vibrate +
|
||||
", vibration=" + vibration +
|
||||
", smallIcon='" + smallIcon + '\'' +
|
||||
", largeIcon='" + largeIcon + '\'' +
|
||||
", playSound=" + playSound +
|
||||
", soundName='" + soundName + '\'' +
|
||||
", soundNames='" + soundNames + '\'' +
|
||||
", color='" + color + '\'' +
|
||||
", scheduleType='" + scheduleType + '\'' +
|
||||
", interval=" + interval +
|
||||
", intervalValue=" + intervalValue +
|
||||
", snoozeInterval=" + snoozeInterval +
|
||||
", tag='" + tag + '\'' +
|
||||
", data='" + data + '\'' +
|
||||
", loopSound=" + loopSound +
|
||||
", useBigText=" + useBigText +
|
||||
", hasButton=" + hasButton +
|
||||
", volume=" + volume +
|
||||
", bypassDnd=" + bypassDnd +
|
||||
", active=" + active +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static AlarmModel fromBundle(@NonNull Bundle bundle) {
|
||||
AlarmModel alarm = new AlarmModel();
|
||||
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
alarm.setAlarmId((int) time);
|
||||
|
||||
alarm.setActive(1);
|
||||
alarm.setAutoCancel(bundle.getBoolean("auto_cancel", true));
|
||||
alarm.setChannel(bundle.getString("channel", "my_channel_id"));
|
||||
alarm.setColor(bundle.getString("color", "red"));
|
||||
|
||||
Bundle data = bundle.getBundle("data");
|
||||
alarm.setData(data);
|
||||
|
||||
alarm.setInterval(bundle.getString("repeat_interval", "hourly"));
|
||||
alarm.setLargeIcon(bundle.getString("large_icon", ""));
|
||||
alarm.setLoopSound(bundle.getBoolean("loop_sound", false));
|
||||
alarm.setMessage(bundle.getString("message", "My Notification Message"));
|
||||
alarm.setPlaySound(bundle.getBoolean("play_sound", true));
|
||||
alarm.setScheduleType(bundle.getString("schedule_type", "once"));
|
||||
alarm.setSmallIcon(bundle.getString("small_icon", "ic_launcher"));
|
||||
alarm.setSnoozeInterval((int) bundle.getDouble("snooze_interval", 1.0));
|
||||
alarm.setSoundName(bundle.getString("sound_name", null));
|
||||
alarm.setSoundNames(bundle.getString("sound_names", null));
|
||||
alarm.setTag(bundle.getString("tag", ""));
|
||||
alarm.setTicker(bundle.getString("ticker", ""));
|
||||
alarm.setTitle(bundle.getString("title", "My Notification Title"));
|
||||
alarm.setVibrate(bundle.getBoolean("vibrate", true));
|
||||
alarm.setHasButton(bundle.getBoolean("has_button", false));
|
||||
alarm.setVibration((int) bundle.getDouble("vibration", 100.0));
|
||||
alarm.setUseBigText(bundle.getBoolean("use_big_text", false));
|
||||
alarm.setVolume(bundle.getDouble("volume", 0.5));
|
||||
alarm.setIntervalValue((int) bundle.getDouble("interval_value", 1));
|
||||
alarm.setBypassDnd(bundle.getBoolean("bypass_dnd", false));
|
||||
|
||||
String datetime = bundle.getString("fire_date");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.ENGLISH);
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
|
||||
try {
|
||||
calendar.setTime(sdf.parse(datetime));
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
alarm.setAlarmDateTime(calendar);
|
||||
return alarm;
|
||||
}
|
||||
|
||||
void setAlarmDateTime(Calendar calendar) {
|
||||
setSecond(calendar.get(Calendar.SECOND));
|
||||
setMinute(calendar.get(Calendar.MINUTE));
|
||||
setHour(calendar.get(Calendar.HOUR_OF_DAY));
|
||||
setDay(calendar.get(Calendar.DAY_OF_MONTH));
|
||||
setMonth(calendar.get(Calendar.MONTH) + 1);
|
||||
setYear(calendar.get(Calendar.YEAR));
|
||||
}
|
||||
|
||||
Calendar getAlarmDateTime() {
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, getHour());
|
||||
calendar.set(Calendar.MINUTE, getMinute());
|
||||
calendar.set(Calendar.SECOND, getSecond());
|
||||
calendar.set(Calendar.DAY_OF_MONTH, getDay());
|
||||
calendar.set(Calendar.MONTH, getMonth() - 1);
|
||||
calendar.set(Calendar.YEAR, getYear());
|
||||
return calendar;
|
||||
}
|
||||
|
||||
Calendar snooze() {
|
||||
Calendar calendar = getAlarmDateTime();
|
||||
calendar.add(Calendar.MINUTE, getSnoozeInterval());
|
||||
setAlarmDateTime(calendar);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
boolean isSameTime(AlarmModel alarm) {
|
||||
return this.getHour() == alarm.getHour() && this.getMinute() == alarm.getMinute() &&
|
||||
this.getSecond() == alarm.getSecond() && this.getDay() == alarm.getDay() &&
|
||||
this.getMonth() == alarm.getMonth() && this.getYear() == alarm.getYear();
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import static com.google.gson.stream.JsonToken.END_OBJECT;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AlarmModelCodec {
|
||||
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
||||
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(final Gson gson, TypeToken<T> typeToken) {
|
||||
if (!Bundle.class.isAssignableFrom(typeToken.getRawType())) {
|
||||
return null;
|
||||
}
|
||||
return (TypeAdapter<T>) new TypeAdapter<Bundle>() {
|
||||
@Override
|
||||
public void write(JsonWriter writer, Bundle bundle) throws IOException {
|
||||
if (bundle == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
writer.beginObject();
|
||||
for (String key : bundle.keySet()) {
|
||||
writer.name(key);
|
||||
Object value = bundle.get(key);
|
||||
if (value == null) {
|
||||
writer.nullValue();
|
||||
} else {
|
||||
gson.toJson(value, value.getClass(), writer);
|
||||
}
|
||||
}
|
||||
writer.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case NULL:
|
||||
reader.nextNull();
|
||||
return null;
|
||||
case BEGIN_OBJECT:
|
||||
return readBundle(reader);
|
||||
default:
|
||||
throw new IOException("Could not read bundle at " + reader.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle readBundle(JsonReader reader) throws IOException {
|
||||
reader.beginObject();
|
||||
Bundle bundle = new Bundle();
|
||||
JsonToken nextToken;
|
||||
while ((nextToken = reader.peek()) != END_OBJECT) {
|
||||
switch (nextToken) {
|
||||
case NAME:
|
||||
readNextKeyValue(reader, bundle);
|
||||
case END_OBJECT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private void readNextKeyValue(JsonReader reader, Bundle bundle) throws IOException {
|
||||
String name = reader.nextName();
|
||||
// only support a small subset of possible types, enough for Joplin
|
||||
switch (reader.peek()) {
|
||||
case STRING:
|
||||
bundle.putString(name, reader.nextString());
|
||||
break;
|
||||
case BOOLEAN:
|
||||
bundle.putBoolean(name, reader.nextBoolean());
|
||||
case NUMBER:
|
||||
double doubleVal = reader.nextDouble();
|
||||
if (Math.round(doubleVal) == doubleVal) {
|
||||
long longVal = (long) doubleVal;
|
||||
if (longVal >= Integer.MIN_VALUE && longVal <= Integer.MAX_VALUE) {
|
||||
bundle.putInt(name, (int) longVal);
|
||||
} else {
|
||||
bundle.putLong(name, longVal);
|
||||
}
|
||||
} else {
|
||||
bundle.putDouble(name, doubleVal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
|
||||
public String toJson(AlarmModel alarmModel) {
|
||||
return gson.toJson(alarmModel);
|
||||
}
|
||||
|
||||
public AlarmModel fromJson(String json) {
|
||||
return gson.fromJson(json, AlarmModel.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AlarmReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent != null) {
|
||||
final AlarmDatabase alarmDB = new AlarmDatabase(context);
|
||||
AlarmUtil alarmUtil = new AlarmUtil((Application) context.getApplicationContext());
|
||||
|
||||
try {
|
||||
String intentType = intent.getExtras().getString("intentType");
|
||||
if (Constants.ADD_INTENT.equals(intentType)) {
|
||||
int id = intent.getExtras().getInt("PendingId");
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
alarmUtil.sendNotification(alarm);
|
||||
alarmUtil.setBootReceiver();
|
||||
|
||||
ArrayList<AlarmModel> alarms = alarmDB.getAlarmList(1);
|
||||
Log.d(Constants.TAG, "alarm start: " + alarm.toString() + ", alarms left: " + alarms.size());
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Failed to add alarm", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Received invalid intent", e);
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
if (action != null) {
|
||||
Log.i(Constants.TAG, "ACTION: " + action);
|
||||
switch (action) {
|
||||
case Constants.NOTIFICATION_ACTION_SNOOZE:
|
||||
int id = intent.getExtras().getInt("SnoozeAlarmId");
|
||||
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
alarmUtil.snoozeAlarm(alarm);
|
||||
Log.i(Constants.TAG, "alarm snoozed: " + alarm.toString());
|
||||
|
||||
alarmUtil.removeFiredNotification(alarm.getId());
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Failed to snooze alarm", e);
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.NOTIFICATION_ACTION_DISMISS:
|
||||
id = intent.getExtras().getInt("AlarmId");
|
||||
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
Log.i(Constants.TAG, "Cancel alarm: " + alarm.toString());
|
||||
|
||||
// emit notification dismissed
|
||||
// TODO also send all user-provided args back
|
||||
ANModule.getReactAppContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("OnNotificationDismissed", "{\"id\": \"" + alarm.getId() + "\"}");
|
||||
|
||||
alarmUtil.removeFiredNotification(alarm.getId());
|
||||
alarmUtil.cancelAlarm(alarm, false); // TODO why false?
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Failed to dismiss alarm", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,476 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Application;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.media.RingtoneManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static com.emekalites.react.alarm.notification.Constants.ADD_INTENT;
|
||||
import static com.emekalites.react.alarm.notification.Constants.NOTIFICATION_ACTION_DISMISS;
|
||||
import static com.emekalites.react.alarm.notification.Constants.NOTIFICATION_ACTION_SNOOZE;
|
||||
|
||||
class AlarmUtil {
|
||||
|
||||
private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
|
||||
private int defaultFlags = 0;
|
||||
|
||||
private final Context context;
|
||||
private final AlarmDatabase alarmDB;
|
||||
|
||||
AlarmUtil(Application context) {
|
||||
this.context = context;
|
||||
this.alarmDB = new AlarmDatabase(context);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
defaultFlags = PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getMainActivityClass() {
|
||||
try {
|
||||
String packageName = context.getPackageName();
|
||||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
||||
String className = launchIntent.getComponent().getClassName();
|
||||
Log.d(Constants.TAG, "main activity classname: " + className);
|
||||
return Class.forName(className);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not load main activity class", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private AlarmManager getAlarmManager() {
|
||||
return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
}
|
||||
|
||||
private NotificationManager getNotificationManager() {
|
||||
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
boolean checkAlarm(ArrayList<AlarmModel> alarms, AlarmModel alarm) {
|
||||
for (AlarmModel aAlarm : alarms) {
|
||||
if (aAlarm.isSameTime(alarm) && aAlarm.getActive() == 1) {
|
||||
Toast.makeText(context, "You have already set this Alarm", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setBootReceiver() {
|
||||
ArrayList<AlarmModel> alarms = alarmDB.getAlarmList(1);
|
||||
if (alarms.size() > 0) {
|
||||
enableBootReceiver(context);
|
||||
} else {
|
||||
disableBootReceiver(context);
|
||||
}
|
||||
}
|
||||
|
||||
void setAlarm(AlarmModel alarm) {
|
||||
Log.i(Constants.TAG, "Set alarm " + alarm);
|
||||
|
||||
Calendar calendar = alarm.getAlarmDateTime();
|
||||
int alarmId = alarm.getAlarmId();
|
||||
|
||||
Intent intent = new Intent(context, AlarmReceiver.class);
|
||||
intent.putExtra("intentType", ADD_INTENT);
|
||||
intent.putExtra("PendingId", alarm.getId());
|
||||
|
||||
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, alarmId, intent, defaultFlags);
|
||||
AlarmManager alarmManager = this.getAlarmManager();
|
||||
|
||||
String scheduleType = alarm.getScheduleType();
|
||||
|
||||
if (scheduleType.equals("once")) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
} else {
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
}
|
||||
} else if (scheduleType.equals("repeat")) {
|
||||
long interval = this.getInterval(alarm.getInterval(), alarm.getIntervalValue());
|
||||
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), interval, alarmIntent);
|
||||
} else {
|
||||
Log.w(Constants.TAG, "Schedule type should either be once or repeat");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setBootReceiver();
|
||||
}
|
||||
|
||||
void snoozeAlarm(AlarmModel alarm) {
|
||||
Log.i(Constants.TAG, "Snooze alarm: " + alarm.toString());
|
||||
|
||||
Calendar calendar = alarm.snooze();
|
||||
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
|
||||
alarm.setAlarmId((int) time);
|
||||
// TODO looks like this sets a new id and then tries to update the row in DB
|
||||
// how's that supposed to work?
|
||||
alarmDB.update(alarm);
|
||||
|
||||
int alarmId = alarm.getAlarmId();
|
||||
|
||||
Intent intent = new Intent(context, AlarmReceiver.class);
|
||||
intent.putExtra("intentType", ADD_INTENT);
|
||||
intent.putExtra("PendingId", alarm.getId());
|
||||
|
||||
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, alarmId, intent, defaultFlags);
|
||||
AlarmManager alarmManager = this.getAlarmManager();
|
||||
|
||||
String scheduleType = alarm.getScheduleType();
|
||||
|
||||
if (scheduleType.equals("once")) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
} else {
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
|
||||
}
|
||||
} else if (scheduleType.equals("repeat")) {
|
||||
long interval = this.getInterval(alarm.getInterval(), alarm.getIntervalValue());
|
||||
|
||||
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), interval, alarmIntent);
|
||||
} else {
|
||||
Log.w(Constants.TAG, "Schedule type should either be once or repeat");
|
||||
}
|
||||
}
|
||||
|
||||
long getInterval(String interval, int value) {
|
||||
long duration = 1;
|
||||
|
||||
switch (interval) {
|
||||
case "minutely":
|
||||
duration = value;
|
||||
break;
|
||||
case "hourly":
|
||||
duration = 60 * value;
|
||||
break;
|
||||
case "daily":
|
||||
duration = 60 * 24;
|
||||
break;
|
||||
case "weekly":
|
||||
duration = 60 * 24 * 7;
|
||||
break;
|
||||
}
|
||||
|
||||
return duration * 60 * 1000;
|
||||
}
|
||||
|
||||
void doCancelAlarm(int id) {
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
this.cancelAlarm(alarm, false);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not cancel alarm with id " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteAlarm(int id) {
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
this.cancelAlarm(alarm, true);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not delete alarm with id " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteRepeatingAlarm(int id) {
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
|
||||
String scheduleType = alarm.getScheduleType();
|
||||
if (scheduleType.equals("repeat")) {
|
||||
this.stopAlarm(alarm);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not delete repeating alarm with id " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
void cancelAlarm(AlarmModel alarm, boolean delete) {
|
||||
String scheduleType = alarm.getScheduleType();
|
||||
if (scheduleType.equals("once") || delete) {
|
||||
this.stopAlarm(alarm);
|
||||
}
|
||||
}
|
||||
|
||||
void stopAlarm(AlarmModel alarm) {
|
||||
AlarmManager alarmManager = this.getAlarmManager();
|
||||
|
||||
int alarmId = alarm.getAlarmId();
|
||||
|
||||
Intent intent = new Intent(context, AlarmReceiver.class);
|
||||
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, alarmId, intent, defaultFlags | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
alarmManager.cancel(alarmIntent);
|
||||
|
||||
alarmDB.delete(alarm.getId());
|
||||
|
||||
this.setBootReceiver();
|
||||
}
|
||||
|
||||
private void enableBootReceiver(Context context) {
|
||||
ComponentName receiver = new ComponentName(context, AlarmBootReceiver.class);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
int setting = pm.getComponentEnabledSetting(receiver);
|
||||
if (setting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
|
||||
setting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
|
||||
Log.i(Constants.TAG, "Enable boot receiver");
|
||||
pm.setComponentEnabledSetting(receiver,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
} else {
|
||||
Log.i(Constants.TAG, "Boot receiver already enabled");
|
||||
}
|
||||
}
|
||||
|
||||
private void disableBootReceiver(Context context) {
|
||||
Log.i(Constants.TAG, "Disable boot receiver");
|
||||
|
||||
ComponentName receiver = new ComponentName(context, AlarmBootReceiver.class);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
pm.setComponentEnabledSetting(receiver,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
private PendingIntent createOnDismissedIntent(Context context, int notificationId) {
|
||||
Intent intent = new Intent(context, AlarmDismissReceiver.class);
|
||||
intent.putExtra(Constants.NOTIFICATION_ID, notificationId);
|
||||
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, defaultFlags);
|
||||
}
|
||||
|
||||
void sendNotification(AlarmModel alarm) {
|
||||
try {
|
||||
Class<?> intentClass = getMainActivityClass();
|
||||
|
||||
if (intentClass == null) {
|
||||
Log.e(Constants.TAG, "No activity class found for the notification");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager mNotificationManager = getNotificationManager();
|
||||
int notificationID = alarm.getAlarmId();
|
||||
|
||||
// title
|
||||
String title = alarm.getTitle();
|
||||
if (title == null || title.equals("")) {
|
||||
ApplicationInfo appInfo = context.getApplicationInfo();
|
||||
title = context.getPackageManager().getApplicationLabel(appInfo).toString();
|
||||
}
|
||||
|
||||
// message
|
||||
// TODO move to AlarmModel constructor?
|
||||
String message = alarm.getMessage();
|
||||
if (message == null || message.equals("")) {
|
||||
Log.e(Constants.TAG, "Cannot send to notification centre because there is no 'message' found");
|
||||
return;
|
||||
}
|
||||
|
||||
// channel
|
||||
// TODO move to AlarmModel constructor?
|
||||
String channelID = alarm.getChannel();
|
||||
if (channelID == null || channelID.equals("")) {
|
||||
Log.e(Constants.TAG, "Cannot send to notification centre because there is no 'channel' found");
|
||||
return;
|
||||
}
|
||||
|
||||
Resources res = context.getResources();
|
||||
String packageName = context.getPackageName();
|
||||
|
||||
//icon
|
||||
// TODO move to AlarmModel constructor?
|
||||
int smallIconResId;
|
||||
String smallIcon = alarm.getSmallIcon();
|
||||
if (smallIcon != null && !smallIcon.equals("")) {
|
||||
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
|
||||
} else {
|
||||
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(context, intentClass);
|
||||
intent.setAction(Constants.NOTIFICATION_ACTION_CLICK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
intent.putExtra(Constants.NOTIFICATION_ID, alarm.getId());
|
||||
intent.putExtra("data", alarm.getData());
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, notificationID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, channelID)
|
||||
.setSmallIcon(smallIconResId)
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setTicker(alarm.getTicker())
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setAutoCancel(alarm.isAutoCancel())
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setSound(null)
|
||||
.setDeleteIntent(createOnDismissedIntent(context, alarm.getId()));
|
||||
|
||||
if (alarm.isPlaySound()) {
|
||||
// TODO use user-supplied sound if available
|
||||
mBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), AudioManager.STREAM_NOTIFICATION);
|
||||
}
|
||||
|
||||
long vibration = alarm.getVibration();
|
||||
|
||||
long[] vibrationPattern = vibration == 0 ? DEFAULT_VIBRATE_PATTERN : new long[]{0, vibration, 1000, vibration};
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel mChannel = new NotificationChannel(channelID, "Alarm Notify", NotificationManager.IMPORTANCE_HIGH);
|
||||
mChannel.enableLights(true);
|
||||
|
||||
String color = alarm.getColor();
|
||||
if (color != null && !color.equals("")) {
|
||||
mChannel.setLightColor(Color.parseColor(color));
|
||||
}
|
||||
|
||||
if (mChannel.canBypassDnd()) {
|
||||
mChannel.setBypassDnd(alarm.isBypassDnd());
|
||||
}
|
||||
|
||||
if (alarm.isVibrate()) {
|
||||
mChannel.setVibrationPattern(vibrationPattern);
|
||||
mChannel.enableVibration(true);
|
||||
}
|
||||
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
mBuilder.setChannelId(channelID);
|
||||
} else {
|
||||
// set vibration
|
||||
mBuilder.setVibrate(alarm.isVibrate() ? vibrationPattern : null);
|
||||
}
|
||||
|
||||
//color
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
String color = alarm.getColor();
|
||||
if (color != null && !color.equals("")) {
|
||||
mBuilder.setColor(Color.parseColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
mBuilder.setContentIntent(pendingIntent);
|
||||
|
||||
if (alarm.isHasButton()) {
|
||||
Intent dismissIntent = new Intent(context, AlarmReceiver.class);
|
||||
dismissIntent.setAction(NOTIFICATION_ACTION_DISMISS);
|
||||
dismissIntent.putExtra("AlarmId", alarm.getId());
|
||||
PendingIntent pendingDismiss = PendingIntent.getBroadcast(context, notificationID, dismissIntent, defaultFlags | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action dismissAction = new NotificationCompat.Action(android.R.drawable.ic_lock_idle_alarm, "DISMISS", pendingDismiss);
|
||||
mBuilder.addAction(dismissAction);
|
||||
|
||||
Intent snoozeIntent = new Intent(context, AlarmReceiver.class);
|
||||
snoozeIntent.setAction(NOTIFICATION_ACTION_SNOOZE);
|
||||
snoozeIntent.putExtra("SnoozeAlarmId", alarm.getId());
|
||||
PendingIntent pendingSnooze = PendingIntent.getBroadcast(context, notificationID, snoozeIntent, defaultFlags | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action snoozeAction = new NotificationCompat.Action(R.drawable.ic_snooze, "SNOOZE", pendingSnooze);
|
||||
mBuilder.addAction(snoozeAction);
|
||||
}
|
||||
|
||||
//use big text
|
||||
if (alarm.isUseBigText()) {
|
||||
mBuilder = mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||
}
|
||||
|
||||
//large icon
|
||||
String largeIcon = alarm.getLargeIcon();
|
||||
if (largeIcon != null && !largeIcon.equals("") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
|
||||
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
||||
if (largeIconResId != 0) {
|
||||
mBuilder.setLargeIcon(largeIconBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
// set tag and push notification
|
||||
Notification notification = mBuilder.build();
|
||||
|
||||
String tag = alarm.getTag();
|
||||
if (tag != null && !tag.equals("")) {
|
||||
mNotificationManager.notify(tag, notificationID, notification);
|
||||
} else {
|
||||
Log.i(Constants.TAG, "Notification done");
|
||||
mNotificationManager.notify(notificationID, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Failed to send notification", e);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFiredNotification(int id) {
|
||||
try {
|
||||
AlarmModel alarm = alarmDB.getAlarm(id);
|
||||
getNotificationManager().cancel(alarm.getAlarmId());
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Could not remove fired notification with id " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
void removeAllFiredNotifications() {
|
||||
getNotificationManager().cancelAll();
|
||||
}
|
||||
|
||||
ArrayList<AlarmModel> getAlarms() {
|
||||
return alarmDB.getAlarmList(1);
|
||||
}
|
||||
|
||||
WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException {
|
||||
WritableMap map = new WritableNativeMap();
|
||||
|
||||
Iterator<String> iterator = jsonObject.keys();
|
||||
while (iterator.hasNext()) {
|
||||
String key = iterator.next();
|
||||
Object value = jsonObject.get(key);
|
||||
if (value instanceof JSONObject) {
|
||||
map.putMap(key, convertJsonToMap((JSONObject) value));
|
||||
} else if (value instanceof Boolean) {
|
||||
map.putBoolean(key, (Boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
map.putInt(key, (Integer) value);
|
||||
} else if (value instanceof Double) {
|
||||
map.putDouble(key, (Double) value);
|
||||
} else if (value instanceof String) {
|
||||
map.putString(key, (String) value);
|
||||
} else {
|
||||
map.putString(key, value.toString());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
class Constants {
|
||||
static final String TAG = "RNAlarmNotification";
|
||||
|
||||
// TODO convert to action
|
||||
static final String ADD_INTENT = "com.emekalites.react.alarm.notification.ADD_INTENT";
|
||||
|
||||
static final String NOTIFICATION_ID = "com.emekalites.react.alarm.notification.NOTIFICATION_ID";
|
||||
|
||||
static final String NOTIFICATION_ACTION_DISMISS = "com.emekalites.react.alarm.notification.ACTION_DISMISS";
|
||||
static final String NOTIFICATION_ACTION_SNOOZE = "com.emekalites.react.alarm.notification.ACTION_SNOOZE";
|
||||
static final String NOTIFICATION_ACTION_CLICK = "com.emekalites.react.alarm.notification.ACTION_CLICK";
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF707070"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.93 6,11v5l-2,2v1h16v-1l-2,-2zM14.5,9.8l-2.8,3.4h2.8L14.5,15h-5v-1.8l2.8,-3.4L9.5,9.8L9.5,8h5v1.8z"/>
|
||||
</vector>
|
@ -0,0 +1,33 @@
|
||||
package com.emekalites.react.alarm.notification;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AlarmModelTest {
|
||||
|
||||
@Test
|
||||
public void testToJson() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("title", "alarm title");
|
||||
bundle.putString("fire_date", "2021-11-27 13:48:00");
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putString("string_data_key", "string_data_value");
|
||||
data.putInt("int_data_key", 42);
|
||||
|
||||
bundle.putBundle("data", data);
|
||||
|
||||
AlarmModel alarm = AlarmModel.fromBundle(bundle);
|
||||
|
||||
AlarmModelCodec codec = new AlarmModelCodec();
|
||||
|
||||
String json = codec.toJson(alarm);
|
||||
System.out.println(json);
|
||||
System.out.println(codec.fromJson(json));
|
||||
}
|
||||
|
||||
}
|
174
packages/react-native-alarm-notification/index.js
vendored
Normal file
174
packages/react-native-alarm-notification/index.js
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const { RNAlarmNotification } = NativeModules;
|
||||
const ReactNativeAN = {};
|
||||
|
||||
const parseDateString = (string) => {
|
||||
const splits = string.split(' ');
|
||||
const dateSplits = splits[0].split('-');
|
||||
const timeSplits = splits[1].split(':');
|
||||
|
||||
const year = dateSplits[2];
|
||||
const month = dateSplits[1];
|
||||
const day = dateSplits[0];
|
||||
|
||||
const hours = timeSplits[0];
|
||||
const minutes = timeSplits[1];
|
||||
const seconds = timeSplits[2];
|
||||
|
||||
return new Date(year, month - 1, day, hours, minutes, seconds);
|
||||
};
|
||||
|
||||
ReactNativeAN.scheduleAlarm = async (details) => {
|
||||
if (!details.fire_date || (details.fire_date && details.fire_date === '')) {
|
||||
throw new Error('failed to schedule alarm because fire date is missing');
|
||||
}
|
||||
|
||||
const past = parseDateString(details.fire_date);
|
||||
const today = new Date();
|
||||
if (past < today) {
|
||||
throw new Error(
|
||||
'failed to schedule alarm because fire date is in the past'
|
||||
);
|
||||
}
|
||||
|
||||
const repeatInterval = details.repeat_interval || 'hourly';
|
||||
const intervalValue = details.interval_value || 1;
|
||||
if (isNaN(intervalValue)) {
|
||||
throw new Error('interval value should be a number');
|
||||
}
|
||||
|
||||
if (
|
||||
repeatInterval === 'minutely' &&
|
||||
(intervalValue < 1 || intervalValue > 59)
|
||||
) {
|
||||
throw new Error('interval value should be between 1 and 59 minutes');
|
||||
}
|
||||
|
||||
if (
|
||||
repeatInterval === 'hourly' &&
|
||||
(intervalValue < 1 || intervalValue > 23)
|
||||
) {
|
||||
throw new Error('interval value should be between 1 and 23 hours');
|
||||
}
|
||||
|
||||
const data = {
|
||||
...details,
|
||||
has_button: details.has_button || false,
|
||||
vibrate: details.vibrate || true,
|
||||
play_sound: details.play_sound || true,
|
||||
schedule_type: details.schedule_type || 'once',
|
||||
repeat_interval: details.repeat_interval || 'hourly',
|
||||
interval_value: details.interval_value || 1,
|
||||
volume: details.volume || 0.5,
|
||||
sound_name: details.sound_name || '',
|
||||
snooze_interval: details.snooze_interval || 1,
|
||||
data: details.data || '',
|
||||
};
|
||||
|
||||
return await RNAlarmNotification.scheduleAlarm(data);
|
||||
};
|
||||
|
||||
ReactNativeAN.sendNotification = (details) => {
|
||||
const data = {
|
||||
...details,
|
||||
has_button: false,
|
||||
vibrate: details.vibrate || true,
|
||||
play_sound: details.play_sound || true,
|
||||
schedule_type: details.schedule_type || 'once',
|
||||
volume: details.volume || 0.5,
|
||||
sound_name: details.sound_name || '',
|
||||
snooze_interval: details.snooze_interval || 1,
|
||||
data: details.data || '',
|
||||
};
|
||||
|
||||
RNAlarmNotification.sendNotification(data);
|
||||
};
|
||||
|
||||
ReactNativeAN.deleteAlarm = (id) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required to delete alarm');
|
||||
}
|
||||
|
||||
RNAlarmNotification.deleteAlarm(id);
|
||||
};
|
||||
|
||||
ReactNativeAN.deleteRepeatingAlarm = (id) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required to delete alarm');
|
||||
}
|
||||
|
||||
RNAlarmNotification.deleteRepeatingAlarm(id);
|
||||
};
|
||||
|
||||
ReactNativeAN.stopAlarmSound = () => {
|
||||
return RNAlarmNotification.stopAlarmSound();
|
||||
};
|
||||
|
||||
ReactNativeAN.removeFiredNotification = (id) => {
|
||||
if (!id) {
|
||||
throw new Error('id is required to remove notification');
|
||||
}
|
||||
|
||||
RNAlarmNotification.removeFiredNotification(id);
|
||||
};
|
||||
|
||||
ReactNativeAN.removeAllFiredNotifications = () => {
|
||||
RNAlarmNotification.removeAllFiredNotifications();
|
||||
};
|
||||
|
||||
ReactNativeAN.getScheduledAlarms = async () => {
|
||||
return await RNAlarmNotification.getScheduledAlarms();
|
||||
};
|
||||
|
||||
// ios request permission
|
||||
ReactNativeAN.requestPermissions = async (permissions) => {
|
||||
let requestedPermissions = {
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
};
|
||||
|
||||
if (permissions) {
|
||||
requestedPermissions = {
|
||||
alert: !!permissions.alert,
|
||||
badge: !!permissions.badge,
|
||||
sound: !!permissions.sound,
|
||||
};
|
||||
}
|
||||
|
||||
return await RNAlarmNotification.requestPermissions(requestedPermissions);
|
||||
};
|
||||
|
||||
// ios check permission
|
||||
ReactNativeAN.checkPermissions = (callback) => {
|
||||
RNAlarmNotification.checkPermissions(callback);
|
||||
};
|
||||
|
||||
ReactNativeAN.parseDate = (rawDate) => {
|
||||
let hours;
|
||||
let day;
|
||||
let month;
|
||||
|
||||
if (rawDate.getHours().toString().length === 1) {
|
||||
hours = `0${rawDate.getHours()}`;
|
||||
} else {
|
||||
hours = `${rawDate.getHours()}`;
|
||||
}
|
||||
|
||||
if (rawDate.getDate().toString().length === 1) {
|
||||
day = `0${rawDate.getDate()}`;
|
||||
} else {
|
||||
day = `${rawDate.getDate()}`;
|
||||
}
|
||||
|
||||
if (rawDate.getMonth().toString().length === 1) {
|
||||
month = `0${rawDate.getMonth() + 1}`;
|
||||
} else {
|
||||
month = `${rawDate.getMonth() + 1}`;
|
||||
}
|
||||
|
||||
return `${day}-${month}-${rawDate.getFullYear()} ${hours}:${rawDate.getMinutes()}:${rawDate.getSeconds()}`;
|
||||
};
|
||||
|
||||
export default ReactNativeAN;
|
@ -0,0 +1,9 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
@interface RnAlarmNotification : RCTEventEmitter <RCTBridgeModule>
|
||||
+ (void)didReceiveNotificationResponse:(UNNotificationResponse *)response API_AVAILABLE(ios(10.0));
|
||||
+ (void)didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios(10.0));
|
||||
@end
|
@ -0,0 +1,794 @@
|
||||
#import "RnAlarmNotification.h"
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
static NSString *const kLocalNotificationReceived = @"LocalNotificationReceived";
|
||||
static NSString *const kLocalNotificationDismissed = @"LocalNotificationDismissed";
|
||||
|
||||
static AVAudioPlayer *player;
|
||||
static id _sharedInstance = nil;
|
||||
|
||||
@implementation RnAlarmNotification
|
||||
|
||||
+(instancetype)sharedInstance {
|
||||
static dispatch_once_t p;
|
||||
dispatch_once(&p, ^{
|
||||
_sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return _sharedInstance;
|
||||
}
|
||||
|
||||
API_AVAILABLE(ios(10.0))
|
||||
static NSDictionary *RCTFormatUNNotification(UNNotification *notification) {
|
||||
NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary];
|
||||
UNNotificationContent *content = notification.request.content;
|
||||
|
||||
formattedNotification[@"id"] = notification.request.identifier;
|
||||
formattedNotification[@"data"] = RCTNullIfNil([content.userInfo objectForKey:@"data"]);
|
||||
|
||||
return formattedNotification;
|
||||
}
|
||||
|
||||
static NSDateComponents *parseDate(NSString *dateString) {
|
||||
NSArray *fire_date = [dateString componentsSeparatedByString:@" "];
|
||||
NSString *date = fire_date[0];
|
||||
NSString *time = fire_date[1];
|
||||
|
||||
NSArray *splitDate = [date componentsSeparatedByString:@"-"];
|
||||
NSArray *splitHour = [time componentsSeparatedByString:@":"];
|
||||
|
||||
NSString *strNumDay = splitDate[0];
|
||||
NSString *strNumMonth = splitDate[1];
|
||||
NSString *strNumYear = splitDate[2];
|
||||
|
||||
NSString *strNumHour = splitHour[0];
|
||||
NSString *strNumMinute = splitHour[1];
|
||||
NSString *strNumSecond = splitHour[2];
|
||||
|
||||
// Configure the trigger for date
|
||||
NSDateComponents *fireDate = [[NSDateComponents alloc] init];
|
||||
fireDate.day = [strNumDay intValue];
|
||||
fireDate.month = [strNumMonth intValue];
|
||||
fireDate.year = [strNumYear intValue];
|
||||
fireDate.hour = [strNumHour intValue];
|
||||
fireDate.minute = [strNumMinute intValue];
|
||||
fireDate.second = [strNumSecond intValue];
|
||||
fireDate.timeZone = [NSTimeZone defaultTimeZone];
|
||||
|
||||
return fireDate;
|
||||
}
|
||||
|
||||
static NSDateComponents *dateToComponents(NSDate *date) {
|
||||
NSDateComponents *fireDate = [[NSDateComponents alloc] init];
|
||||
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
|
||||
[formatter setDateFormat:@"yyyy"];
|
||||
NSString *year = [formatter stringFromDate:date];
|
||||
|
||||
[formatter setDateFormat:@"MM"];
|
||||
NSString *month = [formatter stringFromDate:date];
|
||||
|
||||
[formatter setDateFormat:@"dd"];
|
||||
NSString *day = [formatter stringFromDate:date];
|
||||
|
||||
[formatter setDateFormat:@"HH"];
|
||||
NSString *hour = [formatter stringFromDate:date];
|
||||
|
||||
[formatter setDateFormat:@"mm"];
|
||||
NSString *minute = [formatter stringFromDate:date];
|
||||
|
||||
[formatter setDateFormat:@"ss"];
|
||||
NSString *second = [formatter stringFromDate:date];
|
||||
|
||||
fireDate.day = [day intValue];
|
||||
fireDate.month = [month intValue];
|
||||
fireDate.year = [year intValue];
|
||||
fireDate.hour = [hour intValue];
|
||||
fireDate.minute = [minute intValue];
|
||||
fireDate.second = [second intValue];
|
||||
fireDate.timeZone = [NSTimeZone defaultTimeZone];
|
||||
|
||||
return fireDate;
|
||||
}
|
||||
|
||||
static NSString *stringify(NSDictionary *notification) {
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:notification options:0 error:&error];
|
||||
|
||||
if (! jsonData) {
|
||||
NSLog(@"Got an error: %@", error);
|
||||
return @"bad json";
|
||||
} else {
|
||||
NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue {
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(RNAlarmNotification);
|
||||
|
||||
+ (void)vibratePhone {
|
||||
NSLog(@"vibratePhone %@", @"here");
|
||||
if([[UIDevice currentDevice].model isEqualToString:@"iPhone"]) {
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
|
||||
} else {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void) didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios(10.0)){
|
||||
NSLog(@"content: %@", notification.request.content.userInfo);
|
||||
NSLog(@"alarm id: %@", notification.request.identifier);
|
||||
|
||||
NSNumber *vibrate = [notification.request.content.userInfo objectForKey:@"vibrate"];
|
||||
NSLog(@"vibrate: %@", vibrate);
|
||||
|
||||
NSNumber *sound = [notification.request.content.userInfo objectForKey:@"sound"];
|
||||
|
||||
if([vibrate isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
NSLog(@"do vibrate now");
|
||||
[RnAlarmNotification vibratePhone];
|
||||
}
|
||||
|
||||
if([sound isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
[RnAlarmNotification playSound:notification];
|
||||
}
|
||||
|
||||
NSString *scheduleType = [notification.request.content.userInfo objectForKey:@"schedule_type"];
|
||||
if([scheduleType isEqualToString:@"repeat"]){
|
||||
[RnAlarmNotification repeatAlarm:notification];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||
API_AVAILABLE(ios(10.0)) {
|
||||
NSLog(@"show notification");
|
||||
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
|
||||
if ([response.notification.request.content.categoryIdentifier isEqualToString:@"CUSTOM_ACTIONS"]) {
|
||||
if ([response.actionIdentifier isEqualToString:@"SNOOZE_ACTION"]) {
|
||||
[RnAlarmNotification snoozeAlarm:response.notification];
|
||||
} else if ([response.actionIdentifier isEqualToString:@"DISMISS_ACTION"]) {
|
||||
NSLog(@"do dismiss");
|
||||
[RnAlarmNotification stopSound];
|
||||
|
||||
NSMutableDictionary *notification = [NSMutableDictionary dictionary];
|
||||
notification[@"id"] = response.notification.request.identifier;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationDismissed
|
||||
object:self
|
||||
userInfo:notification];
|
||||
}
|
||||
}
|
||||
|
||||
// send notification
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationReceived
|
||||
object:self
|
||||
userInfo:RCTFormatUNNotification(response.notification)];
|
||||
}
|
||||
|
||||
- (void)startObserving {
|
||||
// receive notification
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleLocalNotificationReceived:) name:kLocalNotificationReceived
|
||||
object:nil];
|
||||
|
||||
// dismiss notification
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleLocalNotificationDismissed:) name:kLocalNotificationDismissed
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[@"OnNotificationOpened", @"OnNotificationDismissed"];
|
||||
}
|
||||
|
||||
- (void)handleLocalNotificationReceived:(NSNotification *)notification {
|
||||
// send to js
|
||||
[self sendEventWithName:@"OnNotificationOpened" body: stringify(notification.userInfo)];
|
||||
}
|
||||
|
||||
- (void)handleLocalNotificationDismissed:(NSNotification *)notification {
|
||||
// send to js
|
||||
[self sendEventWithName:@"OnNotificationDismissed" body: stringify(notification.userInfo)];
|
||||
}
|
||||
|
||||
- (void)stopObserving {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
+ (void)playSound:(UNNotification *)notification API_AVAILABLE(ios(10.0)){
|
||||
@try {
|
||||
NSString *soundName = [notification.request.content.userInfo objectForKey:@"sound_name"];
|
||||
NSNumber *loopSound = [notification.request.content.userInfo objectForKey:@"loop_sound"];
|
||||
NSString *volume = [notification.request.content.userInfo objectForKey:@"volume"];
|
||||
|
||||
NSLog(@"do play sound now: %@", soundName);
|
||||
NSLog(@"loop sound: %@", loopSound);
|
||||
NSLog(@"volume sound: %@", volume);
|
||||
|
||||
// AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
// [session setCategory:AVAudioSessionCategoryPlayback
|
||||
// withOptions:AVAudioSessionCategoryOptionMixWithOthers
|
||||
// error:nil];
|
||||
// [session setActive:true error:nil];
|
||||
//[session setMode:AVAudioSessionModeDefault error:nil]; // optional
|
||||
|
||||
// NSError *playerError = nil;
|
||||
|
||||
// if([RnAlarmNotification checkStringIsNotEmpty:soundName]){
|
||||
// NSLog(@"soundName: %@", soundName);
|
||||
//
|
||||
// NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:soundName];
|
||||
//
|
||||
// NSString* soundPathEscaped = [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
||||
// NSURL *soundUri = [NSURL URLWithString:soundPathEscaped];
|
||||
//
|
||||
// NSLog(@"sound path: %@", soundUri);
|
||||
//
|
||||
// if(player){
|
||||
// [player stop];
|
||||
// player = nil;
|
||||
// }
|
||||
//
|
||||
// player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUri
|
||||
// error:&playerError];
|
||||
//
|
||||
// if(playerError) {
|
||||
// NSLog(@"[AppDelegate] audioPlayerError: %@", playerError);
|
||||
// } else if (player){
|
||||
// @synchronized(self){
|
||||
// player.delegate = (id<AVAudioPlayerDelegate>)self;;
|
||||
// player.enableRate = YES;
|
||||
// [player prepareToPlay];
|
||||
//
|
||||
// NSLog(@"sound volume: %@", RCTNullIfNil(volume));
|
||||
// // set volume
|
||||
// player.volume = [volume floatValue];
|
||||
//
|
||||
// NSLog(@"sound loop: %@", loopSound);
|
||||
// // enable/disable loop
|
||||
// if ([loopSound isEqualToNumber: [NSNumber numberWithInt: 1]]) {
|
||||
// player.numberOfLoops = -1;
|
||||
// } else {
|
||||
// player.numberOfLoops = 0;
|
||||
// }
|
||||
//
|
||||
// [player play];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"%@", exception.reason);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)stopSound {
|
||||
@try {
|
||||
if (player) {
|
||||
[player stop];
|
||||
player.currentTime = 0;
|
||||
}
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"%@", exception.reason);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)repeatAlarm:(UNNotification *)notification API_AVAILABLE(ios(10.0)) {
|
||||
[RnAlarmNotification stopSound];
|
||||
|
||||
@try {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNNotificationContent *contentInfo = notification.request.content;
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = contentInfo.title;
|
||||
content.body = contentInfo.body;
|
||||
|
||||
NSString *scheduleType = [contentInfo.userInfo objectForKey:@"schedule_type"];
|
||||
NSLog(@"schedule type: %@", scheduleType);
|
||||
|
||||
NSNumber *has_button = [contentInfo.userInfo objectForKey:@"has_button"];
|
||||
|
||||
// set buttons
|
||||
if([has_button isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
content.categoryIdentifier = @"CUSTOM_ACTIONS";
|
||||
}
|
||||
|
||||
// set alarm date
|
||||
NSString *fire_date = [contentInfo.userInfo objectForKey:@"fire_date"];
|
||||
|
||||
NSDateComponents *fireDate = parseDate(fire_date);
|
||||
|
||||
NSString *repeat_interval = [contentInfo.userInfo objectForKey:@"repeat_interval"];
|
||||
NSNumber *interval_value = [contentInfo.userInfo objectForKey:@"interval_value"];
|
||||
NSLog(@"schedule repeat interval %@", repeat_interval);
|
||||
|
||||
if([repeat_interval isEqualToString:@"minutely"]){
|
||||
fireDate.minute = fireDate.minute + [interval_value intValue];
|
||||
} else if([repeat_interval isEqualToString:@"hourly"]) {
|
||||
fireDate.hour = fireDate.hour + [interval_value intValue];
|
||||
} else if([repeat_interval isEqualToString:@"daily"]) {
|
||||
fireDate.day = fireDate.day + 1;
|
||||
} else if([repeat_interval isEqualToString:@"weekly"]) {
|
||||
fireDate.weekday = fireDate.weekday + 1;
|
||||
}
|
||||
|
||||
NSLog(@"------ next fire date: %@", fireDate);
|
||||
|
||||
// date to string
|
||||
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
|
||||
NSDate *dateString = [gregorianCalendar dateFromComponents:fireDate];
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateFormat:@"dd-MM-yyyy HH:mm:ss"];
|
||||
NSString *stringFromDate = [formatter stringFromDate:dateString];
|
||||
NSLog(@"%@", stringFromDate);
|
||||
|
||||
UNCalendarNotificationTrigger* trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:fireDate repeats:NO];
|
||||
|
||||
// alarm id
|
||||
NSString *alarmId = [contentInfo.userInfo objectForKey:@"alarmId"];
|
||||
|
||||
NSString *soundName = [contentInfo.userInfo objectForKey:@"sound_name"];
|
||||
NSNumber *playSound = [contentInfo.userInfo objectForKey:@"sound"];
|
||||
|
||||
content.userInfo = @{
|
||||
@"alarmId": alarmId,
|
||||
@"sound": playSound,
|
||||
@"vibrate": [contentInfo.userInfo objectForKey:@"vibrate"],
|
||||
@"data": [contentInfo.userInfo objectForKey:@"data"],
|
||||
@"fire_date": stringFromDate,
|
||||
@"sound_name": soundName,
|
||||
@"loop_sound": [contentInfo.userInfo objectForKey:@"loop_sound"],
|
||||
@"volume": [contentInfo.userInfo objectForKey:@"volume"],
|
||||
@"has_button": [contentInfo.userInfo objectForKey:@"has_button"],
|
||||
@"schedule_type": [contentInfo.userInfo objectForKey:@"schedule_type"],
|
||||
@"repeat_interval": [contentInfo.userInfo objectForKey:@"repeat_interval"],
|
||||
@"interval_value": [contentInfo.userInfo objectForKey:@"interval_value"],
|
||||
@"snooze_interval": [contentInfo.userInfo objectForKey:@"snooze_interval"]
|
||||
};
|
||||
|
||||
if([playSound isEqualToNumber: [NSNumber numberWithInt: 1]]) {
|
||||
BOOL notEmpty = [RnAlarmNotification checkStringIsNotEmpty:soundName];
|
||||
if(notEmpty != YES){
|
||||
content.sound = UNNotificationSound.defaultSound;
|
||||
} else {
|
||||
content.sound = [UNNotificationSound soundNamed:soundName];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the request object.
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:alarmId content:content trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
NSLog(@"error: %@", error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
|
||||
NSDictionary *alarm = [NSDictionary dictionaryWithObjectsAndKeys: alarmId, @"id", nil];
|
||||
NSLog(@"repeat alarm: %@", alarm);
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"error: %@", exception.reason);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)snoozeAlarm:(UNNotification *)notification API_AVAILABLE(ios(10.0)) {
|
||||
NSLog(@"do snooze");
|
||||
[RnAlarmNotification stopSound];
|
||||
|
||||
@try {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNNotificationContent *contentInfo = notification.request.content;
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = contentInfo.title;
|
||||
content.body = contentInfo.body;
|
||||
|
||||
NSNumber *has_button = [contentInfo.userInfo objectForKey:@"has_button"];
|
||||
NSNumber *snooze_interval = [contentInfo.userInfo objectForKey:@"snooze_interval"];
|
||||
|
||||
// set buttons
|
||||
if([has_button isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
content.categoryIdentifier = @"CUSTOM_ACTIONS";
|
||||
}
|
||||
|
||||
// set alarm date
|
||||
int interval = [snooze_interval intValue];
|
||||
NSTimeInterval snoozeInterval = interval * 60;
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
NSDate *newDate = [now dateByAddingTimeInterval:snoozeInterval];
|
||||
NSLog(@"new fire date after snooze: %@", newDate);
|
||||
|
||||
NSDateComponents *newFireDate = dateToComponents(newDate);
|
||||
|
||||
UNCalendarNotificationTrigger* trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:newFireDate repeats:NO];
|
||||
|
||||
NSString *alarmId = [NSString stringWithFormat: @"%ld", (long) NSDate.date.timeIntervalSince1970];
|
||||
|
||||
NSString *soundName = [contentInfo.userInfo objectForKey:@"sound_name"];
|
||||
NSNumber *playSound = [contentInfo.userInfo objectForKey:@"sound"];
|
||||
|
||||
content.userInfo = @{
|
||||
@"alarmId": alarmId,
|
||||
@"sound": playSound,
|
||||
@"vibrate": [contentInfo.userInfo objectForKey:@"vibrate"],
|
||||
@"data": [contentInfo.userInfo objectForKey:@"data"],
|
||||
@"fire_date": [contentInfo.userInfo objectForKey:@"fire_date"],
|
||||
@"sound_name": soundName,
|
||||
@"loop_sound": [contentInfo.userInfo objectForKey:@"loop_sound"],
|
||||
@"volume": [contentInfo.userInfo objectForKey:@"volume"],
|
||||
@"has_button": [contentInfo.userInfo objectForKey:@"has_button"],
|
||||
@"schedule_type": [contentInfo.userInfo objectForKey:@"schedule_type"],
|
||||
@"repeat_interval": [contentInfo.userInfo objectForKey:@"repeat_interval"],
|
||||
@"interval_value": [contentInfo.userInfo objectForKey:@"interval_value"],
|
||||
@"snooze_interval": [contentInfo.userInfo objectForKey:@"snooze_interval"]
|
||||
};
|
||||
|
||||
if([playSound isEqualToNumber: [NSNumber numberWithInt: 1]]) {
|
||||
BOOL notEmpty = [RnAlarmNotification checkStringIsNotEmpty:soundName];
|
||||
if(notEmpty != YES){
|
||||
NSLog(@"use default sound");
|
||||
content.sound = UNNotificationSound.defaultSound;
|
||||
} else {
|
||||
content.sound = [UNNotificationSound soundNamed:soundName];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the request object.
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:alarmId content:content trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
NSLog(@"error: %@", error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
|
||||
NSDictionary *alarm = [NSDictionary dictionaryWithObjectsAndKeys: alarmId, @"id", nil];
|
||||
NSLog(@"snooze alarm: %@", alarm);
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"error: %@", exception.reason);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL) checkStringIsNotEmpty:(NSString*)string {
|
||||
if (string == (id)[NSNull null] || string.length == 0) return NO;
|
||||
return YES;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(scheduleAlarm: (NSDictionary *)details resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
|
||||
@try {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = [NSString localizedUserNotificationStringForKey:details[@"title"] arguments:nil];
|
||||
content.body = [NSString localizedUserNotificationStringForKey:details[@"message"] arguments:nil];
|
||||
|
||||
// set buttons
|
||||
if([details[@"has_button"] isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
content.categoryIdentifier = @"CUSTOM_ACTIONS";
|
||||
}
|
||||
|
||||
// set alarm date
|
||||
NSDateComponents *fireDate = parseDate(details[@"fire_date"]);
|
||||
|
||||
UNCalendarNotificationTrigger* trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:fireDate repeats:NO];
|
||||
|
||||
// alarm id
|
||||
NSString *alarmId = [NSString stringWithFormat: @"%ld", (long) NSDate.date.timeIntervalSince1970];
|
||||
|
||||
NSString *volume = [details[@"volume"] stringValue];
|
||||
|
||||
content.userInfo = @{
|
||||
@"alarmId": alarmId,
|
||||
@"sound": details[@"play_sound"],
|
||||
@"vibrate": details[@"vibrate"],
|
||||
@"data": details[@"data"],
|
||||
@"fire_date": details[@"fire_date"],
|
||||
@"sound_name": details[@"sound_name"],
|
||||
@"loop_sound": details[@"loop_sound"],
|
||||
@"volume": volume,
|
||||
@"has_button": details[@"has_button"],
|
||||
@"schedule_type": details[@"schedule_type"],
|
||||
@"repeat_interval": details[@"repeat_interval"],
|
||||
@"interval_value": details[@"interval_value"],
|
||||
@"snooze_interval": details[@"snooze_interval"]
|
||||
};
|
||||
|
||||
if([details[@"play_sound"] isEqualToNumber: [NSNumber numberWithInt: 1]]) {
|
||||
BOOL notEmpty = [RnAlarmNotification checkStringIsNotEmpty:details[@"sound_name"]];
|
||||
if(notEmpty != YES){
|
||||
content.sound = UNNotificationSound.defaultSound;
|
||||
} else {
|
||||
content.sound = [UNNotificationSound soundNamed:details[@"sound_name"]];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the request object.
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:alarmId content:content trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
NSLog(@"error: %@", error.localizedDescription);
|
||||
reject(@"error", nil, error);
|
||||
}
|
||||
}];
|
||||
|
||||
NSDictionary *alarm = [NSDictionary dictionaryWithObjectsAndKeys: alarmId, @"id", nil];
|
||||
|
||||
resolve(alarm);
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"%@", exception.reason);
|
||||
NSMutableDictionary * info = [NSMutableDictionary dictionary];
|
||||
[info setValue:exception.name forKey:@"ExceptionName"];
|
||||
[info setValue:exception.reason forKey:@"ExceptionReason"];
|
||||
[info setValue:exception.callStackSymbols forKey:@"ExceptionCallStackSymbols"];
|
||||
[info setValue:exception.userInfo forKey:@"ExceptionUserInfo"];
|
||||
|
||||
NSError *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:info];
|
||||
reject(@"error", nil, error);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(sendNotification: (NSDictionary *)details) {
|
||||
@try {
|
||||
NSLog(@"send notification now");
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = [NSString localizedUserNotificationStringForKey:details[@"title"] arguments:nil];
|
||||
content.body = [NSString localizedUserNotificationStringForKey:details[@"message"] arguments:nil];
|
||||
|
||||
// set buttons
|
||||
if([details[@"has_button"] isEqualToNumber: [NSNumber numberWithInt: 1]]){
|
||||
content.categoryIdentifier = @"CUSTOM_ACTIONS";
|
||||
}
|
||||
|
||||
// alarm id
|
||||
NSString *alarmId = [NSString stringWithFormat: @"%ld", (long) NSDate.date.timeIntervalSince1970];
|
||||
|
||||
NSString *volume = [details[@"volume"] stringValue];
|
||||
|
||||
content.userInfo = @{
|
||||
@"alarmId": alarmId,
|
||||
@"sound": details[@"play_sound"],
|
||||
@"vibrate": details[@"vibrate"],
|
||||
@"data": details[@"data"],
|
||||
@"sound_name": details[@"sound_name"],
|
||||
@"loop_sound": details[@"loop_sound"],
|
||||
@"volume": volume,
|
||||
@"schedule_type": @"once"
|
||||
};
|
||||
|
||||
if([details[@"play_sound"] isEqualToNumber: [NSNumber numberWithInt: 1]]) {
|
||||
BOOL notEmpty = [RnAlarmNotification checkStringIsNotEmpty:details[@"sound_name"]];
|
||||
if(notEmpty != YES){
|
||||
content.sound = UNNotificationSound.defaultSound;
|
||||
} else {
|
||||
content.sound = [UNNotificationSound soundNamed:details[@"sound_name"]];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the request object.
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:alarmId content:content trigger:nil];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { }];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} @catch(NSException *exception){
|
||||
NSLog(@"error: %@", exception.reason);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(deleteAlarm: (NSInteger *)id){
|
||||
NSLog(@"delete alarm: %li", (long) id);
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
NSArray *array = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%li", (long)id], nil];
|
||||
[center removePendingNotificationRequestsWithIdentifiers:array];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(deleteRepeatingAlarm: (NSInteger *)id){
|
||||
NSLog(@"delete alarm: %li", (long) id);
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
NSArray *array = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%li", (long)id], nil];
|
||||
[center removePendingNotificationRequestsWithIdentifiers:array];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopAlarmSound){
|
||||
NSLog(@"stop alarm sound");
|
||||
[RnAlarmNotification stopSound];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeFiredNotification: (NSInteger)id){
|
||||
NSLog(@"remove fired notification: %li", (long) id);
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
NSArray *array = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%li", (long)id], nil];
|
||||
[center removeDeliveredNotificationsWithIdentifiers:array];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeAllFiredNotifications){
|
||||
NSLog(@"remove all notifications");
|
||||
if (@available(iOS 10.0, *)) {
|
||||
if ([UNUserNotificationCenter class]) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center removeAllDeliveredNotifications];
|
||||
}
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
API_AVAILABLE(ios(10.0))
|
||||
static NSDictionary *RCTFormatUNNotificationRequest(UNNotificationRequest *request)
|
||||
{
|
||||
NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary];
|
||||
UNNotificationContent *content = request.content;
|
||||
|
||||
NSDateComponents *fireDate = parseDate(content.userInfo[@"fire_date"]);
|
||||
|
||||
formattedNotification[@"id"] = request.identifier;
|
||||
formattedNotification[@"day"] = [NSString stringWithFormat:@"%li", (long)fireDate.day];
|
||||
formattedNotification[@"month"] = [NSString stringWithFormat:@"%li", (long)fireDate.month];
|
||||
formattedNotification[@"year"] = [NSString stringWithFormat:@"%li", (long)fireDate.year];
|
||||
formattedNotification[@"hour"] = [NSString stringWithFormat:@"%li", (long)fireDate.hour];
|
||||
formattedNotification[@"minute"] =[NSString stringWithFormat:@"%li", (long)fireDate.minute];
|
||||
formattedNotification[@"second"] = [NSString stringWithFormat:@"%li", (long)fireDate.second];
|
||||
|
||||
return formattedNotification;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getScheduledAlarms: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
|
||||
NSLog(@"get all notifications");
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> * _Nonnull requests) {
|
||||
NSLog(@"count%lu",(unsigned long)requests.count);
|
||||
|
||||
NSMutableArray<NSDictionary *> *formattedNotifications = [NSMutableArray new];
|
||||
|
||||
for (UNNotificationRequest *request in requests) {
|
||||
[formattedNotifications addObject:RCTFormatUNNotificationRequest(request)];
|
||||
}
|
||||
resolve(formattedNotifications);
|
||||
}];
|
||||
} else {
|
||||
resolve(nil);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
if (RCTRunningInAppExtension()) {
|
||||
reject(@"E_UNABLE_TO_REQUEST_PERMISSIONS", nil, RCTErrorWithMessage(@"Requesting push notifications is currently unavailable in an app extension"));
|
||||
return;
|
||||
}
|
||||
|
||||
UIUserNotificationType types = UIUserNotificationTypeNone;
|
||||
if (permissions) {
|
||||
if ([RCTConvert BOOL:permissions[@"alert"]]) {
|
||||
types |= UIUserNotificationTypeAlert;
|
||||
}
|
||||
if ([RCTConvert BOOL:permissions[@"badge"]]) {
|
||||
types |= UIUserNotificationTypeBadge;
|
||||
}
|
||||
if ([RCTConvert BOOL:permissions[@"sound"]]) {
|
||||
types |= UIUserNotificationTypeSound;
|
||||
}
|
||||
} else {
|
||||
types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
|
||||
}
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
UNNotificationCategory* generalCategory = [UNNotificationCategory
|
||||
categoryWithIdentifier:@"GENERAL"
|
||||
actions:@[]
|
||||
intentIdentifiers:@[]
|
||||
options:UNNotificationCategoryOptionCustomDismissAction];
|
||||
|
||||
UNNotificationAction* snoozeAction = [UNNotificationAction
|
||||
actionWithIdentifier:@"SNOOZE_ACTION"
|
||||
title:@"SNOOZE"
|
||||
options:UNNotificationActionOptionNone];
|
||||
|
||||
UNNotificationAction* stopAction = [UNNotificationAction
|
||||
actionWithIdentifier:@"DISMISS_ACTION"
|
||||
title:@"DISMISS"
|
||||
options:UNNotificationActionOptionForeground];
|
||||
|
||||
UNNotificationCategory* customCategory = [UNNotificationCategory
|
||||
categoryWithIdentifier:@"CUSTOM_ACTIONS"
|
||||
actions:@[snoozeAction, stopAction]
|
||||
intentIdentifiers:@[]
|
||||
options:UNNotificationCategoryOptionNone];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center setNotificationCategories:[NSSet setWithObjects:generalCategory, customCategory, nil]];
|
||||
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UIUserNotificationTypeBadge + UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
||||
|
||||
if (error != NULL) {
|
||||
reject(@"-1", @"Error - Push authorization request failed.", error);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void){
|
||||
[RCTSharedApplication() registerForRemoteNotifications];
|
||||
});
|
||||
[UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
|
||||
resolve(RCTPromiseResolveValueForUNNotificationSettings(settings));
|
||||
}];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
resolve(nil);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) {
|
||||
if (RCTRunningInAppExtension()) {
|
||||
callback(@[RCTSettingsDictForUNNotificationSettings(NO, NO, NO, NO, NO)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
[UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
|
||||
callback(@[RCTPromiseResolveValueForUNNotificationSettings(settings)]);
|
||||
}];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
API_AVAILABLE(ios(10.0))
|
||||
static inline NSDictionary *RCTPromiseResolveValueForUNNotificationSettings(UNNotificationSettings* _Nonnull settings) {
|
||||
return RCTSettingsDictForUNNotificationSettings(settings.alertSetting == UNNotificationSettingEnabled, settings.badgeSetting == UNNotificationSettingEnabled, settings.soundSetting == UNNotificationSettingEnabled, settings.lockScreenSetting == UNNotificationSettingEnabled, settings.notificationCenterSetting == UNNotificationSettingEnabled);
|
||||
}
|
||||
|
||||
static inline NSDictionary *RCTSettingsDictForUNNotificationSettings(BOOL alert, BOOL badge, BOOL sound, BOOL lockScreen, BOOL notificationCenter) {
|
||||
return @{@"alert": @(alert), @"badge": @(badge), @"sound": @(sound), @"lockScreen": @(lockScreen), @"notificationCenter": @(notificationCenter)};
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,290 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8D0B4EFD24DC22BA00A18E45 /* RnAlarmNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D0B4EFB24DC22B900A18E45 /* RnAlarmNotification.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
58B511D91A9E6C8500147676 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
134814201AA4EA6300B7C361 /* libRnAlarmNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRnAlarmNotification.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8D0B4EFB24DC22B900A18E45 /* RnAlarmNotification.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RnAlarmNotification.m; sourceTree = "<group>"; };
|
||||
8D0B4EFC24DC22B900A18E45 /* RnAlarmNotification.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RnAlarmNotification.h; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
58B511D81A9E6C8500147676 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
134814211AA4EA7D00B7C361 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134814201AA4EA6300B7C361 /* libRnAlarmNotification.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
8D0B4EFC24DC22B900A18E45 /* RnAlarmNotification.h */,
|
||||
8D0B4EFB24DC22B900A18E45 /* RnAlarmNotification.m */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
58B511DA1A9E6C8500147676 /* RnAlarmNotification */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RnAlarmNotification" */;
|
||||
buildPhases = (
|
||||
58B511D71A9E6C8500147676 /* Sources */,
|
||||
58B511D81A9E6C8500147676 /* Frameworks */,
|
||||
58B511D91A9E6C8500147676 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RnAlarmNotification;
|
||||
productName = RCTDataManager;
|
||||
productReference = 134814201AA4EA6300B7C361 /* libRnAlarmNotification.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
58B511D31A9E6C8500147676 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
58B511DA1A9E6C8500147676 = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RnAlarmNotification" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 58B511D21A9E6C8500147676;
|
||||
productRefGroup = 58B511D21A9E6C8500147676;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
58B511DA1A9E6C8500147676 /* RnAlarmNotification */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
58B511D71A9E6C8500147676 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8D0B4EFD24DC22BA00A18E45 /* RnAlarmNotification.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
58B511ED1A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511EE1A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../../React/**",
|
||||
"$(SRCROOT)/../../react-native/React/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RnAlarmNotification;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../../React/**",
|
||||
"$(SRCROOT)/../../react-native/React/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RnAlarmNotification;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RnAlarmNotification" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511ED1A9E6C8500147676 /* Debug */,
|
||||
58B511EE1A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RnAlarmNotification" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511F01A9E6C8500147676 /* Debug */,
|
||||
58B511F11A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:RnAlarmNotification.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
20
packages/react-native-alarm-notification/package.json
Normal file
20
packages/react-native-alarm-notification/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@joplin/react-native-alarm-notification",
|
||||
"title": "React Native Alarm Notification for Joplin. Forked from https://github.com/emekalites/react-native-alarm-notification",
|
||||
"version": "2.10.0",
|
||||
"description": "schedule alarm with notification in react-native",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/roman-r-m/react-native-alarm-notification.git",
|
||||
"baseUrl": "https://github.com/roman-r-m/react-native-alarm-notification"
|
||||
},
|
||||
"license": "MIT",
|
||||
"licenseFilename": "LICENSE",
|
||||
"readmeFilename": "README.md",
|
||||
"devDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.70.6"
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "react-native-alarm-notification"
|
||||
s.version = package["version"]
|
||||
s.summary = package["description"]
|
||||
s.description = <<-DESC
|
||||
react-native-alarm-notification
|
||||
DESC
|
||||
s.homepage = "https://github.com/laurent22/joplin/tree/dev/packages/react-native-alarm-notification"
|
||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
||||
s.authors = { "Chukwuemeka Ihedoro" => "caihedoro@gmail.com" }
|
||||
s.platforms = { :ios => "9.0" }
|
||||
s.source = { :git => "https://github.com/laurent22/joplin.git" }
|
||||
|
||||
s.source_files = "ios/**/*.{h,c,m,swift}"
|
||||
s.requires_arc = true
|
||||
|
||||
s.dependency "React"
|
||||
# ...
|
||||
# s.dependency "..."
|
||||
end
|
||||
|
@ -131,13 +131,14 @@ async function main() {
|
||||
await updatePackageVersion(`${rootDir}/packages/app-mobile/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/generator-joplin/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/htmlpack/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/react-native-saf-x/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/lib/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/pdf-viewer/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/plugin-repo-cli/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/react-native-alarm-notification/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/react-native-saf-x/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/renderer/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/server/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/tools/package.json`, majorMinorVersion, options);
|
||||
await updatePackageVersion(`${rootDir}/packages/pdf-viewer/package.json`, majorMinorVersion, options);
|
||||
|
||||
if (options.updateVersion) {
|
||||
await updateGradleVersion(`${rootDir}/packages/app-mobile/android/app/build.gradle`, majorMinorVersion);
|
||||
|
18
yarn.lock
18
yarn.lock
@ -4774,6 +4774,7 @@ __metadata:
|
||||
"@codemirror/state": 6.1.4
|
||||
"@codemirror/view": 6.7.1
|
||||
"@joplin/lib": ~2.10
|
||||
"@joplin/react-native-alarm-notification": ~2.10
|
||||
"@joplin/react-native-saf-x": ~2.10
|
||||
"@joplin/renderer": ~2.10
|
||||
"@joplin/tools": ~2.10
|
||||
@ -4800,7 +4801,6 @@ __metadata:
|
||||
jest: 29.3.1
|
||||
jest-environment-jsdom: 29.3.1
|
||||
jetifier: 2.0.0
|
||||
joplin-rn-alarm-notification: 1.0.7
|
||||
jsc-android: 241213.1.0
|
||||
jsdom: 20.0.0
|
||||
lodash: 4.17.21
|
||||
@ -5051,6 +5051,15 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/react-native-alarm-notification@workspace:packages/react-native-alarm-notification, @joplin/react-native-alarm-notification@~2.10":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/react-native-alarm-notification@workspace:packages/react-native-alarm-notification"
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-native: 0.70.6
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/react-native-saf-x@workspace:packages/react-native-saf-x, @joplin/react-native-saf-x@~2.10":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/react-native-saf-x@workspace:packages/react-native-saf-x"
|
||||
@ -20466,13 +20475,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"joplin-rn-alarm-notification@npm:1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "joplin-rn-alarm-notification@npm:1.0.7"
|
||||
checksum: 68e9cb15fc9dcd4d6b7a523967adcad8bc120da260d80c9cb541128b3e499f529dd7b879f5e35edf81d8e992194ab20d61281a835e0a15cde114a2cc208f8128
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"joplin@workspace:packages/app-cli":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "joplin@workspace:packages/app-cli"
|
||||
|
Loading…
Reference in New Issue
Block a user