From 753fce32ed6e74581370f42bce3922ca1f450e06 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 4 Dec 2020 16:55:23 -0800 Subject: [PATCH 01/36] generate push-notifications --- lerna.json | 1 + push-notifications/.eslintignore | 2 + push-notifications/.gitignore | 61 ++ push-notifications/.prettierignore | 2 + .../CapacitorPushNotifications.podspec | 17 + push-notifications/LICENSE | 23 + push-notifications/README.md | 19 + push-notifications/android/.gitignore | 1 + push-notifications/android/build.gradle | 58 ++ push-notifications/android/gradle.properties | 24 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + push-notifications/android/gradlew | 183 ++++++ push-notifications/android/gradlew.bat | 100 +++ push-notifications/android/proguard-rules.pro | 21 + push-notifications/android/settings.gradle | 2 + .../android/ExampleInstrumentedTest.java | 26 + .../android/src/main/AndroidManifest.xml | 3 + .../pushnotifications/PushNotifications.java | 8 + .../PushNotificationsPlugin.java | 22 + .../android/src/main/res/.gitkeep | 0 .../com/getcapacitor/ExampleUnitTest.java | 18 + .../ios/Plugin.xcodeproj/project.pbxproj | 569 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Plugin.xcscheme | 77 +++ .../xcschemes/PluginTests.xcscheme | 68 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + push-notifications/ios/Plugin/Info.plist | 24 + .../ios/Plugin/PushNotifications.swift | 7 + .../ios/Plugin/PushNotificationsPlugin.h | 10 + .../ios/Plugin/PushNotificationsPlugin.m | 8 + .../ios/Plugin/PushNotificationsPlugin.swift | 18 + push-notifications/ios/PluginTests/Info.plist | 22 + .../PushNotificationsPluginTests.swift | 25 + push-notifications/ios/Podfile | 16 + push-notifications/package.json | 84 +++ push-notifications/rollup.config.js | 14 + push-notifications/src/__tests__/example.ts | 5 + push-notifications/src/definitions.ts | 3 + push-notifications/src/index.ts | 12 + push-notifications/src/web.ts | 12 + push-notifications/tsconfig.json | 23 + 44 files changed, 1626 insertions(+) create mode 100644 push-notifications/.eslintignore create mode 100644 push-notifications/.gitignore create mode 100644 push-notifications/.prettierignore create mode 100644 push-notifications/CapacitorPushNotifications.podspec create mode 100644 push-notifications/LICENSE create mode 100644 push-notifications/README.md create mode 100644 push-notifications/android/.gitignore create mode 100644 push-notifications/android/build.gradle create mode 100644 push-notifications/android/gradle.properties create mode 100644 push-notifications/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 push-notifications/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 push-notifications/android/gradlew create mode 100644 push-notifications/android/gradlew.bat create mode 100644 push-notifications/android/proguard-rules.pro create mode 100644 push-notifications/android/settings.gradle create mode 100644 push-notifications/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java create mode 100644 push-notifications/android/src/main/AndroidManifest.xml create mode 100644 push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java create mode 100644 push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java create mode 100644 push-notifications/android/src/main/res/.gitkeep create mode 100644 push-notifications/android/src/test/java/com/getcapacitor/ExampleUnitTest.java create mode 100644 push-notifications/ios/Plugin.xcodeproj/project.pbxproj create mode 100644 push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme create mode 100644 push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme create mode 100644 push-notifications/ios/Plugin.xcworkspace/contents.xcworkspacedata create mode 100644 push-notifications/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 push-notifications/ios/Plugin/Info.plist create mode 100644 push-notifications/ios/Plugin/PushNotifications.swift create mode 100644 push-notifications/ios/Plugin/PushNotificationsPlugin.h create mode 100644 push-notifications/ios/Plugin/PushNotificationsPlugin.m create mode 100644 push-notifications/ios/Plugin/PushNotificationsPlugin.swift create mode 100644 push-notifications/ios/PluginTests/Info.plist create mode 100644 push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift create mode 100644 push-notifications/ios/Podfile create mode 100644 push-notifications/package.json create mode 100644 push-notifications/rollup.config.js create mode 100644 push-notifications/src/__tests__/example.ts create mode 100644 push-notifications/src/definitions.ts create mode 100644 push-notifications/src/index.ts create mode 100644 push-notifications/src/web.ts create mode 100644 push-notifications/tsconfig.json diff --git a/lerna.json b/lerna.json index 5142f71a6..ddd14a6b3 100644 --- a/lerna.json +++ b/lerna.json @@ -12,6 +12,7 @@ "keyboard", "motion", "network", + "push-notifications", "screen-reader", "share", "status-bar", diff --git a/push-notifications/.eslintignore b/push-notifications/.eslintignore new file mode 100644 index 000000000..9d0b71a3c --- /dev/null +++ b/push-notifications/.eslintignore @@ -0,0 +1,2 @@ +build +dist diff --git a/push-notifications/.gitignore b/push-notifications/.gitignore new file mode 100644 index 000000000..70ccbf713 --- /dev/null +++ b/push-notifications/.gitignore @@ -0,0 +1,61 @@ +# node files +dist +node_modules + +# iOS files +Pods +Podfile.lock +Build +xcuserdata + +# macOS files +.DS_Store + + + +# Based on Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin +gen +out + +# Gradle files +.gradle +build + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation + +# Android Studio captures folder +captures + +# IntelliJ +*.iml +.idea + +# Keystore files +# Uncomment the following line if you do not want to check your keystore files in. +#*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild diff --git a/push-notifications/.prettierignore b/push-notifications/.prettierignore new file mode 100644 index 000000000..9d0b71a3c --- /dev/null +++ b/push-notifications/.prettierignore @@ -0,0 +1,2 @@ +build +dist diff --git a/push-notifications/CapacitorPushNotifications.podspec b/push-notifications/CapacitorPushNotifications.podspec new file mode 100644 index 000000000..efe3f3782 --- /dev/null +++ b/push-notifications/CapacitorPushNotifications.podspec @@ -0,0 +1,17 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'CapacitorPushNotifications' + s.version = package['version'] + s.summary = package['description'] + s.license = package['license'] + s.homepage = package['repository']['url'] + s.author = package['author'] + s.source = { :git => package['repository']['url'], :tag => s.version.to_s } + s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' + s.ios.deployment_target = '11.0' + s.dependency 'Capacitor' + s.swift_version = '5.1' +end diff --git a/push-notifications/LICENSE b/push-notifications/LICENSE new file mode 100644 index 000000000..6652495cb --- /dev/null +++ b/push-notifications/LICENSE @@ -0,0 +1,23 @@ +Copyright 2020-present Ionic +https://ionic.io + +MIT License + +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. diff --git a/push-notifications/README.md b/push-notifications/README.md new file mode 100644 index 000000000..335b3d0d6 --- /dev/null +++ b/push-notifications/README.md @@ -0,0 +1,19 @@ +# @capacitor/push-notifications + +The Push Notifications API provides access to native push notifications. + +## Install + +```bash +npm install @capacitor/push-notifications +npx cap sync +``` + +## API + + + + + + + diff --git a/push-notifications/android/.gitignore b/push-notifications/android/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/push-notifications/android/.gitignore @@ -0,0 +1 @@ +/build diff --git a/push-notifications/android/build.gradle b/push-notifications/android/build.gradle new file mode 100644 index 000000000..f2f9dac80 --- /dev/null +++ b/push-notifications/android/build.gradle @@ -0,0 +1,58 @@ +ext { + junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.1.0' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0' +} + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 29 + defaultConfig { + minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 21 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + google() + jcenter() + mavenCentral() +} + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':capacitor-android') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" +} diff --git a/push-notifications/android/gradle.properties b/push-notifications/android/gradle.properties new file mode 100644 index 000000000..0566c221d --- /dev/null +++ b/push-notifications/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.jar b/push-notifications/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.properties b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..4e1cc9db6 --- /dev/null +++ b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/push-notifications/android/gradlew b/push-notifications/android/gradlew new file mode 100755 index 000000000..2fe81a7d9 --- /dev/null +++ b/push-notifications/android/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/push-notifications/android/gradlew.bat b/push-notifications/android/gradlew.bat new file mode 100644 index 000000000..9618d8d96 --- /dev/null +++ b/push-notifications/android/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/push-notifications/android/proguard-rules.pro b/push-notifications/android/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/push-notifications/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/push-notifications/android/settings.gradle b/push-notifications/android/settings.gradle new file mode 100644 index 000000000..1e5b8431f --- /dev/null +++ b/push-notifications/android/settings.gradle @@ -0,0 +1,2 @@ +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') \ No newline at end of file diff --git a/push-notifications/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/push-notifications/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java new file mode 100644 index 000000000..58020e16c --- /dev/null +++ b/push-notifications/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.getcapacitor.android; + +import static org.junit.Assert.*; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.getcapacitor.android", appContext.getPackageName()); + } +} diff --git a/push-notifications/android/src/main/AndroidManifest.xml b/push-notifications/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..0259c07d0 --- /dev/null +++ b/push-notifications/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java new file mode 100644 index 000000000..735be6ae6 --- /dev/null +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java @@ -0,0 +1,8 @@ +package com.capacitorjs.plugins.pushnotifications; + +public class PushNotifications { + + public String echo(String value) { + return value; + } +} diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java new file mode 100644 index 000000000..a4320820c --- /dev/null +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java @@ -0,0 +1,22 @@ +package com.capacitorjs.plugins.pushnotifications; + +import com.getcapacitor.JSObject; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.CapacitorPlugin; + +@CapacitorPlugin(name = "PushNotifications") +public class PushNotificationsPlugin extends Plugin { + + private PushNotifications implementation = new PushNotifications(); + + @PluginMethod + public void echo(PluginCall call) { + String value = call.getString("value"); + + JSObject ret = new JSObject(); + ret.put("value", implementation.echo(value)); + call.resolve(ret); + } +} diff --git a/push-notifications/android/src/main/res/.gitkeep b/push-notifications/android/src/main/res/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/push-notifications/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/push-notifications/android/src/test/java/com/getcapacitor/ExampleUnitTest.java new file mode 100644 index 000000000..a0fed0cfb --- /dev/null +++ b/push-notifications/android/src/test/java/com/getcapacitor/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.getcapacitor; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj new file mode 100644 index 000000000..c27768d6f --- /dev/null +++ b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj @@ -0,0 +1,569 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */; }; + 20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */; }; + 2F98D68224C9AAE500613A4C /* PushNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F98D68124C9AAE400613A4C /* PushNotifications.swift */; }; + 50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFF88201F53D600D50D53 /* Plugin.framework */; }; + 50ADFF97201F53D600D50D53 /* PushNotificationsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFF96201F53D600D50D53 /* PushNotificationsPluginTests.swift */; }; + 50ADFF99201F53D600D50D53 /* PushNotificationsPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFFA52020D75100D50D53 /* Capacitor.framework */; }; + 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */; }; + 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 50ADFF93201F53D600D50D53 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50ADFF7F201F53D600D50D53 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 50ADFF87201F53D600D50D53; + remoteInfo = Plugin; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2F98D68124C9AAE400613A4C /* PushNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotifications.swift; sourceTree = ""; }; + 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 50ADFF88201F53D600D50D53 /* Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PushNotificationsPlugin.h; sourceTree = ""; }; + 50ADFF8C201F53D600D50D53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50ADFF91201F53D600D50D53 /* PluginTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PluginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 50ADFF96201F53D600D50D53 /* PushNotificationsPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsPluginTests.swift; sourceTree = ""; }; + 50ADFF98201F53D600D50D53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50ADFFA52020D75100D50D53 /* Capacitor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Capacitor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PushNotificationsPlugin.m; sourceTree = ""; }; + 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationsPlugin.swift; sourceTree = ""; }; + 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.debug.xcconfig"; sourceTree = ""; }; + 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.release.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.release.xcconfig"; sourceTree = ""; }; + 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.debug.xcconfig"; sourceTree = ""; }; + F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.release.xcconfig"; sourceTree = ""; }; + F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 50ADFF84201F53D600D50D53 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */, + 03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50ADFF8E201F53D600D50D53 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */, + 20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 50ADFF7E201F53D600D50D53 = { + isa = PBXGroup; + children = ( + 50ADFF8A201F53D600D50D53 /* Plugin */, + 50ADFF95201F53D600D50D53 /* PluginTests */, + 50ADFF89201F53D600D50D53 /* Products */, + 8C8E7744173064A9F6D438E3 /* Pods */, + A797B9EFA3DCEFEA1FBB66A9 /* Frameworks */, + ); + sourceTree = ""; + }; + 50ADFF89201F53D600D50D53 /* Products */ = { + isa = PBXGroup; + children = ( + 50ADFF88201F53D600D50D53 /* Plugin.framework */, + 50ADFF91201F53D600D50D53 /* PluginTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 50ADFF8A201F53D600D50D53 /* Plugin */ = { + isa = PBXGroup; + children = ( + 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */, + 2F98D68124C9AAE400613A4C /* PushNotifications.swift */, + 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */, + 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */, + 50ADFF8C201F53D600D50D53 /* Info.plist */, + ); + path = Plugin; + sourceTree = ""; + }; + 50ADFF95201F53D600D50D53 /* PluginTests */ = { + isa = PBXGroup; + children = ( + 50ADFF96201F53D600D50D53 /* PushNotificationsPluginTests.swift */, + 50ADFF98201F53D600D50D53 /* Info.plist */, + ); + path = PluginTests; + sourceTree = ""; + }; + 8C8E7744173064A9F6D438E3 /* Pods */ = { + isa = PBXGroup; + children = ( + 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */, + 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */, + 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */, + F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + A797B9EFA3DCEFEA1FBB66A9 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 50ADFFA52020D75100D50D53 /* Capacitor.framework */, + 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */, + F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 50ADFF85201F53D600D50D53 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 50ADFF99201F53D600D50D53 /* PushNotificationsPlugin.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 50ADFF87201F53D600D50D53 /* Plugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = 50ADFF9C201F53D600D50D53 /* Build configuration list for PBXNativeTarget "Plugin" */; + buildPhases = ( + AB5B3E54B4E897F32C2279DA /* [CP] Check Pods Manifest.lock */, + 50ADFF83201F53D600D50D53 /* Sources */, + 50ADFF84201F53D600D50D53 /* Frameworks */, + 50ADFF85201F53D600D50D53 /* Headers */, + 50ADFF86201F53D600D50D53 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Plugin; + productName = Plugin; + productReference = 50ADFF88201F53D600D50D53 /* Plugin.framework */; + productType = "com.apple.product-type.framework"; + }; + 50ADFF90201F53D600D50D53 /* PluginTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 50ADFF9F201F53D600D50D53 /* Build configuration list for PBXNativeTarget "PluginTests" */; + buildPhases = ( + 0596884F929ED6F1DE134961 /* [CP] Check Pods Manifest.lock */, + 50ADFF8D201F53D600D50D53 /* Sources */, + 50ADFF8E201F53D600D50D53 /* Frameworks */, + 50ADFF8F201F53D600D50D53 /* Resources */, + 8E97F58B69A94C6503FC9C85 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 50ADFF94201F53D600D50D53 /* PBXTargetDependency */, + ); + name = PluginTests; + productName = PluginTests; + productReference = 50ADFF91201F53D600D50D53 /* PluginTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 50ADFF7F201F53D600D50D53 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1160; + ORGANIZATIONNAME = "Max Lynch"; + TargetAttributes = { + 50ADFF87201F53D600D50D53 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + }; + 50ADFF90201F53D600D50D53 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 50ADFF82201F53D600D50D53 /* Build configuration list for PBXProject "Plugin" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 50ADFF7E201F53D600D50D53; + productRefGroup = 50ADFF89201F53D600D50D53 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 50ADFF87201F53D600D50D53 /* Plugin */, + 50ADFF90201F53D600D50D53 /* PluginTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 50ADFF86201F53D600D50D53 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50ADFF8F201F53D600D50D53 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0596884F929ED6F1DE134961 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PluginTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8E97F58B69A94C6503FC9C85 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Capacitor/Capacitor.framework", + "${BUILT_PRODUCTS_DIR}/CapacitorCordova/Cordova.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Capacitor.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cordova.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + AB5B3E54B4E897F32C2279DA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Plugin-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 50ADFF83201F53D600D50D53 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */, + 2F98D68224C9AAE500613A4C /* PushNotifications.swift in Sources */, + 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 50ADFF8D201F53D600D50D53 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50ADFF97201F53D600D50D53 /* PushNotificationsPluginTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 50ADFF94201F53D600D50D53 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 50ADFF87201F53D600D50D53 /* Plugin */; + targetProxy = 50ADFF93201F53D600D50D53 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 50ADFF9A201F53D600D50D53 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "\"${BUILT_PRODUCTS_DIR}/Capacitor\"", + "\"${BUILT_PRODUCTS_DIR}/CapacitorCordova\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 50ADFF9B201F53D600D50D53 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "\"${BUILT_PRODUCTS_DIR}/Capacitor\"", + "\"${BUILT_PRODUCTS_DIR}/CapacitorCordova\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + 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 = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 50ADFF9D201F53D600D50D53 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Plugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)"; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 50ADFF9E201F53D600D50D53 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Plugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; + ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 50ADFFA0201F53D600D50D53 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = PluginTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.PluginTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 50ADFFA1201F53D600D50D53 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = PluginTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.PluginTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 50ADFF82201F53D600D50D53 /* Build configuration list for PBXProject "Plugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 50ADFF9A201F53D600D50D53 /* Debug */, + 50ADFF9B201F53D600D50D53 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 50ADFF9C201F53D600D50D53 /* Build configuration list for PBXNativeTarget "Plugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 50ADFF9D201F53D600D50D53 /* Debug */, + 50ADFF9E201F53D600D50D53 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 50ADFF9F201F53D600D50D53 /* Build configuration list for PBXNativeTarget "PluginTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 50ADFFA0201F53D600D50D53 /* Debug */, + 50ADFFA1201F53D600D50D53 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 50ADFF7F201F53D600D50D53 /* Project object */; +} diff --git a/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/push-notifications/ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme new file mode 100644 index 000000000..303f2621b --- /dev/null +++ b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme new file mode 100644 index 000000000..3d8c88d25 --- /dev/null +++ b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/push-notifications/ios/Plugin.xcworkspace/contents.xcworkspacedata b/push-notifications/ios/Plugin.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..afad624ec --- /dev/null +++ b/push-notifications/ios/Plugin.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/push-notifications/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/push-notifications/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/push-notifications/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/push-notifications/ios/Plugin/Info.plist b/push-notifications/ios/Plugin/Info.plist new file mode 100644 index 000000000..1007fd9dd --- /dev/null +++ b/push-notifications/ios/Plugin/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/push-notifications/ios/Plugin/PushNotifications.swift b/push-notifications/ios/Plugin/PushNotifications.swift new file mode 100644 index 000000000..cf71feb22 --- /dev/null +++ b/push-notifications/ios/Plugin/PushNotifications.swift @@ -0,0 +1,7 @@ +import Foundation + +@objc public class PushNotifications: NSObject { + @objc public func echo(_ value: String) -> String { + return value + } +} diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.h b/push-notifications/ios/Plugin/PushNotificationsPlugin.h new file mode 100644 index 000000000..f2bd9e0bb --- /dev/null +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.h @@ -0,0 +1,10 @@ +#import + +//! Project version number for Plugin. +FOUNDATION_EXPORT double PluginVersionNumber; + +//! Project version string for Plugin. +FOUNDATION_EXPORT const unsigned char PluginVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.m b/push-notifications/ios/Plugin/PushNotificationsPlugin.m new file mode 100644 index 000000000..2a2caeb12 --- /dev/null +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.m @@ -0,0 +1,8 @@ +#import +#import + +// Define the plugin using the CAP_PLUGIN Macro, and +// each method the plugin supports using the CAP_PLUGIN_METHOD macro. +CAP_PLUGIN(PushNotificationsPlugin, "PushNotifications", + CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise); +) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift new file mode 100644 index 000000000..c2af8ede9 --- /dev/null +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -0,0 +1,18 @@ +import Foundation +import Capacitor + +/** + * Please read the Capacitor iOS Plugin Development Guide + * here: https://capacitorjs.com/docs/plugins/ios + */ +@objc(PushNotificationsPlugin) +public class PushNotificationsPlugin: CAPPlugin { + private let implementation = PushNotifications() + + @objc func echo(_ call: CAPPluginCall) { + let value = call.getString("value") ?? "" + call.resolve([ + "value": implementation.echo(value) + ]) + } +} diff --git a/push-notifications/ios/PluginTests/Info.plist b/push-notifications/ios/PluginTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/push-notifications/ios/PluginTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift b/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift new file mode 100644 index 000000000..2d0750d3d --- /dev/null +++ b/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift @@ -0,0 +1,25 @@ +import XCTest +@testable import Plugin + +class PushNotificationsTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testEcho() { + // This is an example of a functional test case for a plugin. + // Use XCTAssert and related functions to verify your tests produce the correct results. + + let implementation = PushNotifications() + let value = "Hello, World!" + let result = implementation.echo(value) + + XCTAssertEqual(value, result) + } +} diff --git a/push-notifications/ios/Podfile b/push-notifications/ios/Podfile new file mode 100644 index 000000000..350751435 --- /dev/null +++ b/push-notifications/ios/Podfile @@ -0,0 +1,16 @@ +platform :ios, '11.0' + +def capacitor_pods + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + pod 'Capacitor', :path => '../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' +end + +target 'Plugin' do + capacitor_pods +end + +target 'PluginTests' do + capacitor_pods +end diff --git a/push-notifications/package.json b/push-notifications/package.json new file mode 100644 index 000000000..002f90644 --- /dev/null +++ b/push-notifications/package.json @@ -0,0 +1,84 @@ +{ + "name": "@capacitor/push-notifications", + "version": "0.0.1", + "description": "The Push Notifications API provides access to native push notifications.", + "main": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "unpkg": "dist/plugin.js", + "files": [ + "android/src/main/", + "android/build.gradle", + "dist/", + "ios/Plugin/", + "CapacitorPushNotifications.podspec" + ], + "author": "Ionic ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/ionic-team/capacitor-plugins.git" + }, + "bugs": { + "url": "https://github.com/ionic-team/capacitor-plugins/issues" + }, + "keywords": [ + "capacitor", + "plugin", + "native" + ], + "scripts": { + "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", + "verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin && cd ..", + "verify:android": "cd android && ./gradlew clean build test && cd ..", + "verify:web": "npm run build && npm test", + "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint", + "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- autocorrect --format", + "eslint": "eslint . --ext ts", + "prettier": "prettier \"**/*.{css,html,ts,js,java}\"", + "swiftlint": "node-swiftlint", + "docgen": "docgen --api PushNotificationsPlugin --output-readme README.md --output-json dist/docs.json", + "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.js", + "clean": "rimraf ./dist", + "watch": "tsc --watch", + "prepublishOnly": "npm run build", + "test": "jest" + }, + "devDependencies": { + "@capacitor/android": "next", + "@capacitor/core": "next", + "@capacitor/docgen": "^0.0.10", + "@capacitor/ios": "next", + "@ionic/eslint-config": "^0.3.0", + "@ionic/prettier-config": "^1.0.1", + "@ionic/swiftlint-config": "^1.1.2", + "@types/jest": "^26.0.15", + "eslint": "^7.11.0", + "jest": "^26.6.3", + "prettier": "~2.2.0", + "prettier-plugin-java": "~1.0.0", + "rimraf": "^3.0.2", + "rollup": "^2.32.0", + "swiftlint": "^1.0.1", + "ts-jest": "^26.4.4", + "typescript": "~4.0.3" + }, + "peerDependencies": { + "@capacitor/core": "next" + }, + "prettier": "@ionic/prettier-config", + "swiftlint": "@ionic/swiftlint-config", + "eslintConfig": { + "extends": "@ionic/eslint-config/recommended" + }, + "capacitor": { + "ios": { + "src": "ios" + }, + "android": { + "src": "android" + } + }, + "jest": { + "preset": "ts-jest" + } +} diff --git a/push-notifications/rollup.config.js b/push-notifications/rollup.config.js new file mode 100644 index 000000000..3a9cc34d2 --- /dev/null +++ b/push-notifications/rollup.config.js @@ -0,0 +1,14 @@ +export default { + input: 'dist/esm/index.js', + output: { + file: 'dist/plugin.js', + format: 'iife', + name: 'capacitorPushNotifications', + globals: { + '@capacitor/core': 'capacitorExports', + }, + sourcemap: true, + inlineDynamicImports: true, + }, + external: ['@capacitor/core'], +}; diff --git a/push-notifications/src/__tests__/example.ts b/push-notifications/src/__tests__/example.ts new file mode 100644 index 000000000..51e12a407 --- /dev/null +++ b/push-notifications/src/__tests__/example.ts @@ -0,0 +1,5 @@ +describe('example', () => { + it('Addition is correct', () => { + expect(2 + 2).toEqual(4); + }); +}); diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts new file mode 100644 index 000000000..f2e8bb551 --- /dev/null +++ b/push-notifications/src/definitions.ts @@ -0,0 +1,3 @@ +export interface PushNotificationsPlugin { + echo(options: { value: string }): Promise<{ value: string }>; +} diff --git a/push-notifications/src/index.ts b/push-notifications/src/index.ts new file mode 100644 index 000000000..fcaa29240 --- /dev/null +++ b/push-notifications/src/index.ts @@ -0,0 +1,12 @@ +import { registerPlugin } from '@capacitor/core'; + +import type { PushNotificationsPlugin } from './definitions'; + +const PushNotifications = registerPlugin( + 'PushNotifications', + { + web: () => import('./web').then(m => new m.PushNotificationsWeb()), + }, +); + +export { PushNotifications }; diff --git a/push-notifications/src/web.ts b/push-notifications/src/web.ts new file mode 100644 index 000000000..b65a8d9c8 --- /dev/null +++ b/push-notifications/src/web.ts @@ -0,0 +1,12 @@ +import { WebPlugin } from '@capacitor/core'; + +import type { PushNotificationsPlugin } from './definitions'; + +export class PushNotificationsWeb + extends WebPlugin + implements PushNotificationsPlugin { + async echo(options: { value: string }): Promise<{ value: string }> { + console.log('ECHO', options); + return options; + } +} diff --git a/push-notifications/tsconfig.json b/push-notifications/tsconfig.json new file mode 100644 index 000000000..538e088fd --- /dev/null +++ b/push-notifications/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "declaration": true, + "esModuleInterop": true, + "lib": [ + "dom" + ], + "module": "es2015", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist/esm", + "pretty": true, + "sourceMap": true, + "strict": true, + "target": "es2017" + }, + "files": [ + "src/index.ts" + ] +} From 3a9038b2861a1fbf721922e59a7cb1cf1e7d0de4 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 4 Dec 2020 17:07:25 -0800 Subject: [PATCH 02/36] use proper versions --- push-notifications/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/push-notifications/package.json b/push-notifications/package.json index 002f90644..a0e2e54ba 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -44,10 +44,10 @@ "test": "jest" }, "devDependencies": { - "@capacitor/android": "next", - "@capacitor/core": "next", + "@capacitor/android": "^3.0.0-alpha.7", + "@capacitor/core": "^3.0.0-alpha.7", "@capacitor/docgen": "^0.0.10", - "@capacitor/ios": "next", + "@capacitor/ios": "^3.0.0-alpha.7", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^1.0.1", "@ionic/swiftlint-config": "^1.1.2", From 027a733c06c087ae50dc7c1ff27317453ca8c638 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 4 Dec 2020 17:08:30 -0800 Subject: [PATCH 03/36] gradle update --- push-notifications/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 58910 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- push-notifications/android/gradlew | 2 ++ push-notifications/android/gradlew.bat | 4 ++++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/push-notifications/android/build.gradle b/push-notifications/android/build.gradle index f2f9dac80..be79102f6 100644 --- a/push-notifications/android/build.gradle +++ b/push-notifications/android/build.gradle @@ -11,7 +11,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.1' } } diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.jar b/push-notifications/android/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..62d4c053550b91381bbd28b1afc82d634bf73a8a 100644 GIT binary patch delta 12524 zcmY*pqN z|ERss*;ReIR@Lg&r_R7IT-GRDbpR4NMKNcwFgy&*A|eco1PnBHRth?SBneiw6$UT&^LVJ0?Naa`KfV(>F!Ez}~S z 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 delta 12163 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 diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.properties b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties index 4e1cc9db6..186b71557 100644 --- a/push-notifications/android/gradle/wrapper/gradle-wrapper.properties +++ b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/push-notifications/android/gradlew b/push-notifications/android/gradlew index 2fe81a7d9..fbd7c5158 100755 --- a/push-notifications/android/gradlew +++ b/push-notifications/android/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/push-notifications/android/gradlew.bat b/push-notifications/android/gradlew.bat index 9618d8d96..5093609d5 100644 --- a/push-notifications/android/gradlew.bat +++ b/push-notifications/android/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% From 5b4ec2c9254bd73b6dfa076ba666188552e45087 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Sun, 6 Dec 2020 13:25:55 -0800 Subject: [PATCH 04/36] use esnext modules --- push-notifications/tsconfig.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/push-notifications/tsconfig.json b/push-notifications/tsconfig.json index 538e088fd..3bb999d96 100644 --- a/push-notifications/tsconfig.json +++ b/push-notifications/tsconfig.json @@ -3,10 +3,8 @@ "allowUnreachableCode": false, "declaration": true, "esModuleInterop": true, - "lib": [ - "dom" - ], - "module": "es2015", + "lib": ["dom"], + "module": "esnext", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, @@ -17,7 +15,5 @@ "strict": true, "target": "es2017" }, - "files": [ - "src/index.ts" - ] + "files": ["src/index.ts"] } From 9ba78cddc5ac28a762a3792317985759394b0b6d Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Sun, 6 Dec 2020 15:11:08 -0800 Subject: [PATCH 05/36] android working --- push-notifications/README.md | 313 +++++++++++++++++- push-notifications/android/build.gradle | 2 + .../NotificationChannelManager.java | 127 +++++++ .../pushnotifications/PushNotifications.java | 8 - .../PushNotificationsPlugin.java | 243 +++++++++++++- push-notifications/src/definitions.ts | 152 ++++++++- push-notifications/src/index.ts | 4 +- push-notifications/src/web.ts | 12 - 8 files changed, 827 insertions(+), 34 deletions(-) create mode 100644 push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java delete mode 100644 push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java delete mode 100644 push-notifications/src/web.ts diff --git a/push-notifications/README.md b/push-notifications/README.md index 335b3d0d6..a72e6c3d8 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -11,9 +11,316 @@ npx cap sync ## API - + + +* [`register()`](#register) +* [`getDeliveredNotifications()`](#getdeliverednotifications) +* [`removeDeliveredNotifications(...)`](#removedeliverednotifications) +* [`removeAllDeliveredNotifications()`](#removealldeliverednotifications) +* [`createChannel(...)`](#createchannel) +* [`deleteChannel(...)`](#deletechannel) +* [`listChannels()`](#listchannels) +* [`checkPermissions()`](#checkpermissions) +* [`requestPermissions()`](#requestpermissions) +* [`addListener(...)`](#addlistener) +* [`addListener(...)`](#addlistener) +* [`addListener(...)`](#addlistener) +* [`addListener(...)`](#addlistener) +* [`removeAllListeners()`](#removealllisteners) +* [Interfaces](#interfaces) + + - - + + +### register() + +```typescript +register() => Promise +``` + +Register the app to receive push notifications. +Will trigger registration event with the push token +or registrationError if there was some problem. +Doesn't prompt the user for notification permissions, use requestPermission() first. + +-------------------- + + +### getDeliveredNotifications() + +```typescript +getDeliveredNotifications() => Promise +``` + +Returns the notifications that are visible on the notifications screen. + +**Returns:** Promise<PushNotificationDeliveredList> + +-------------------- + + +### removeDeliveredNotifications(...) + +```typescript +removeDeliveredNotifications(delivered: PushNotificationDeliveredList) => Promise +``` + +Removes the specified notifications from the notifications screen. + +| Param | Type | Description | +| --------------- | --------------------------------------------------------------------------------------- | -------------------------------- | +| **`delivered`** | PushNotificationDeliveredList | list of delivered notifications. | + +-------------------- + + +### removeAllDeliveredNotifications() + +```typescript +removeAllDeliveredNotifications() => Promise +``` + +Removes all the notifications from the notifications screen. + +-------------------- + + +### createChannel(...) + +```typescript +createChannel(channel: Channel) => Promise +``` + +On Android O or newer (SDK 26+) creates a notification channel. + +| Param | Type | Description | +| ------------- | ------------------------------------------- | ----------- | +| **`channel`** | Channel | to create. | + +-------------------- + + +### deleteChannel(...) + +```typescript +deleteChannel(channel: Channel) => Promise +``` + +On Android O or newer (SDK 26+) deletes a notification channel. + +| Param | Type | Description | +| ------------- | ------------------------------------------- | ----------- | +| **`channel`** | Channel | to delete. | + +-------------------- + + +### listChannels() + +```typescript +listChannels() => Promise +``` + +On Android O or newer (SDK 26+) list the available notification channels. + +**Returns:** Promise<ListChannelsResult> + +-------------------- + + +### checkPermissions() + +```typescript +checkPermissions() => Promise +``` + +Check permission to receive push notifications. + +**Returns:** Promise<PermissionStatus> + +**Since:** 1.0.0 + +-------------------- + + +### requestPermissions() + +```typescript +requestPermissions() => Promise +``` + +Request permission to receive push notifications. + +**Returns:** Promise<PermissionStatus> + +**Since:** 1.0.0 + +-------------------- + + +### addListener(...) + +```typescript +addListener(eventName: 'registration', listenerFunc: (token: PushNotificationToken) => void) => PluginListenerHandle +``` + +Event called when the push notification registration finished without problems. +Provides the push notification token. + +| Param | Type | Description | +| ------------------ | ------------------------------------------------------------------------------------------- | ----------------------------- | +| **`eventName`** | "registration" | registration. | +| **`listenerFunc`** | (token: PushNotificationToken) => void | callback with the push token. | + +**Returns:** PluginListenerHandle + +-------------------- + + +### addListener(...) + +```typescript +addListener(eventName: 'registrationError', listenerFunc: (error: any) => void) => PluginListenerHandle +``` + +Event called when the push notification registration finished with problems. +Provides an error with the registration problem. + +| Param | Type | Description | +| ------------------ | ------------------------------------ | ------------------------------------- | +| **`eventName`** | "registrationError" | registrationError. | +| **`listenerFunc`** | (error: any) => void | callback with the registration error. | + +**Returns:** PluginListenerHandle + +-------------------- + + +### addListener(...) + +```typescript +addListener(eventName: 'pushNotificationReceived', listenerFunc: (notification: PushNotificationSchema) => void) => PluginListenerHandle +``` + +Event called when the device receives a push notification. + +| Param | Type | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| **`eventName`** | "pushNotificationReceived" | pushNotificationReceived. | +| **`listenerFunc`** | (notification: PushNotificationSchema) => void | callback with the received notification. | + +**Returns:** PluginListenerHandle + +-------------------- + + +### addListener(...) + +```typescript +addListener(eventName: 'pushNotificationActionPerformed', listenerFunc: (notification: PushNotificationActionPerformed) => void) => PluginListenerHandle +``` + +Event called when an action is performed on a pusn notification. + +| Param | Type | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| **`eventName`** | "pushNotificationActionPerformed" | pushNotificationActionPerformed. | +| **`listenerFunc`** | (notification: PushNotificationActionPerformed) => void | callback with the notification action. | + +**Returns:** PluginListenerHandle + +-------------------- + + +### removeAllListeners() + +```typescript +removeAllListeners() => void +``` + +Remove all native listeners for this plugin. + +-------------------- + + +### Interfaces + + +#### PushNotificationDeliveredList + +| Prop | Type | +| ------------------- | ------------------------------------- | +| **`notifications`** | PushNotificationSchema[] | + + +#### PushNotificationSchema + +| Prop | Type | Description | +| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------- | +| **`title`** | string | | +| **`subtitle`** | string | | +| **`body`** | string | | +| **`id`** | string | | +| **`badge`** | number | | +| **`notification`** | any | | +| **`data`** | any | | +| **`click_action`** | string | | +| **`link`** | string | | +| **`group`** | string | Android only: set the group identifier for notification grouping, like threadIdentifier on iOS. | +| **`groupSummary`** | boolean | Android only: designate this notification as the summary for a group (should be used with the `group` property). | + + +#### Channel + +| Prop | Type | +| ----------------- | ---------------------------------- | +| **`id`** | string | +| **`name`** | string | +| **`description`** | string | +| **`sound`** | string | +| **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | +| **`visibility`** | 0 \| 1 \| -1 | +| **`lights`** | boolean | +| **`lightColor`** | string | +| **`vibration`** | boolean | + + +#### ListChannelsResult + +| Prop | Type | +| -------------- | ---------------------- | +| **`channels`** | Channel[] | + + +#### PermissionStatus + +| Prop | Type | +| ------------- | ------------------------------------------------------------------------- | +| **`receive`** | "prompt" \| "prompt-with-rationale" \| "granted" \| "denied" | + + +#### PluginListenerHandle + +| Prop | Type | +| ------------ | -------------------------- | +| **`remove`** | () => void | + + +#### PushNotificationToken + +| Prop | Type | +| ----------- | ------------------- | +| **`value`** | string | + + +#### PushNotificationActionPerformed + +| Prop | Type | +| ------------------ | ------------------------------------------------------------------------- | +| **`actionId`** | string | +| **`inputValue`** | string | +| **`notification`** | PushNotificationSchema | + diff --git a/push-notifications/android/build.gradle b/push-notifications/android/build.gradle index be79102f6..17237e5cc 100644 --- a/push-notifications/android/build.gradle +++ b/push-notifications/android/build.gradle @@ -3,6 +3,7 @@ ext { androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.1.0' androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0' + firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '20.1.2' } buildscript { @@ -52,6 +53,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':capacitor-android') implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "com.google.firebase:firebase-messaging:$firebaseMessagingVersion" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java new file mode 100644 index 000000000..72ef52bc0 --- /dev/null +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java @@ -0,0 +1,127 @@ +package com.capacitorjs.plugins.pushnotifications; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Color; +import android.media.AudioAttributes; +import android.net.Uri; +import androidx.core.app.NotificationCompat; +import com.getcapacitor.JSArray; +import com.getcapacitor.JSObject; +import com.getcapacitor.Logger; +import com.getcapacitor.PluginCall; +import java.util.List; + +public class NotificationChannelManager { + + private Context context; + private NotificationManager notificationManager; + + public NotificationChannelManager(Context context, NotificationManager manager) { + this.context = context; + this.notificationManager = manager; + } + + private static String CHANNEL_ID = "id"; + private static String CHANNEL_NAME = "name"; + private static String CHANNEL_DESCRIPTION = "description"; + private static String CHANNEL_IMPORTANCE = "importance"; + private static String CHANNEL_VISIBILITY = "visibility"; + private static String CHANNEL_SOUND = "sound"; + private static String CHANNEL_VIBRATE = "vibration"; + private static String CHANNEL_USE_LIGHTS = "lights"; + private static String CHANNEL_LIGHT_COLOR = "lightColor"; + + public void createChannel(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + JSObject channel = new JSObject(); + channel.put(CHANNEL_ID, call.getString(CHANNEL_ID)); + channel.put(CHANNEL_NAME, call.getString(CHANNEL_NAME)); + channel.put(CHANNEL_DESCRIPTION, call.getString(CHANNEL_DESCRIPTION, "")); + channel.put(CHANNEL_VISIBILITY, call.getInt(CHANNEL_VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC)); + channel.put(CHANNEL_IMPORTANCE, call.getInt(CHANNEL_IMPORTANCE)); + channel.put(CHANNEL_SOUND, call.getString(CHANNEL_SOUND, null)); + channel.put(CHANNEL_VIBRATE, call.getBoolean(CHANNEL_VIBRATE, false)); + channel.put(CHANNEL_USE_LIGHTS, call.getBoolean(CHANNEL_USE_LIGHTS, false)); + channel.put(CHANNEL_LIGHT_COLOR, call.getString(CHANNEL_LIGHT_COLOR, null)); + createChannel(channel); + call.success(); + } else { + call.unavailable(); + } + } + + public void createChannel(JSObject channel) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + NotificationChannel notificationChannel = new NotificationChannel( + channel.getString(CHANNEL_ID), + channel.getString(CHANNEL_NAME), + channel.getInteger(CHANNEL_IMPORTANCE) + ); + notificationChannel.setDescription(channel.getString(CHANNEL_DESCRIPTION)); + notificationChannel.setLockscreenVisibility(channel.getInteger(CHANNEL_VISIBILITY)); + notificationChannel.enableVibration(channel.getBool(CHANNEL_VIBRATE)); + notificationChannel.enableLights(channel.getBool(CHANNEL_USE_LIGHTS)); + String lightColor = channel.getString(CHANNEL_LIGHT_COLOR); + if (lightColor != null) { + try { + notificationChannel.setLightColor(Color.parseColor(lightColor)); + } catch (IllegalArgumentException ex) { + Logger.error(Logger.tags("NotificationChannel"), "Invalid color provided for light color.", null); + } + } + String sound = channel.getString(CHANNEL_SOUND, null); + if (sound != null && !sound.isEmpty()) { + if (sound.contains(".")) { + sound = sound.substring(0, sound.lastIndexOf('.')); + } + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build(); + Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + sound); + notificationChannel.setSound(soundUri, audioAttributes); + } + notificationManager.createNotificationChannel(notificationChannel); + } + } + + public void deleteChannel(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + String channelId = call.getString("id"); + notificationManager.deleteNotificationChannel(channelId); + call.success(); + } else { + call.unavailable(); + } + } + + public void listChannels(PluginCall call) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + List notificationChannels = notificationManager.getNotificationChannels(); + JSArray channels = new JSArray(); + for (NotificationChannel notificationChannel : notificationChannels) { + JSObject channel = new JSObject(); + channel.put(CHANNEL_ID, notificationChannel.getId()); + channel.put(CHANNEL_NAME, notificationChannel.getName()); + channel.put(CHANNEL_DESCRIPTION, notificationChannel.getDescription()); + channel.put(CHANNEL_IMPORTANCE, notificationChannel.getImportance()); + channel.put(CHANNEL_VISIBILITY, notificationChannel.getLockscreenVisibility()); + channel.put(CHANNEL_SOUND, notificationChannel.getSound()); + channel.put(CHANNEL_VIBRATE, notificationChannel.shouldVibrate()); + channel.put(CHANNEL_USE_LIGHTS, notificationChannel.shouldShowLights()); + channel.put(CHANNEL_LIGHT_COLOR, String.format("#%06X", (0xFFFFFF & notificationChannel.getLightColor()))); + Logger.debug(Logger.tags("NotificationChannel"), "visibility " + notificationChannel.getLockscreenVisibility()); + Logger.debug(Logger.tags("NotificationChannel"), "importance " + notificationChannel.getImportance()); + channels.put(channel); + } + JSObject result = new JSObject(); + result.put("channels", channels); + call.success(result); + } else { + call.unavailable(); + } + } +} diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java deleted file mode 100644 index 735be6ae6..000000000 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotifications.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.capacitorjs.plugins.pushnotifications; - -public class PushNotifications { - - public String echo(String value) { - return value; - } -} diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java index a4320820c..089ab83c5 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java @@ -1,22 +1,251 @@ package com.capacitorjs.plugins.pushnotifications; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.service.notification.StatusBarNotification; +import com.getcapacitor.Bridge; +import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginHandle; import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; +import com.getcapacitor.annotation.Permission; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.RemoteMessage; +import java.util.ArrayList; +import java.util.List; +import org.json.JSONException; +import org.json.JSONObject; -@CapacitorPlugin(name = "PushNotifications") +@CapacitorPlugin(name = "PushNotifications", permissions = @Permission(strings = {}, alias = "receive")) public class PushNotificationsPlugin extends Plugin { - private PushNotifications implementation = new PushNotifications(); + public static Bridge staticBridge = null; + public static RemoteMessage lastMessage = null; + public NotificationManager notificationManager; + private NotificationChannelManager notificationChannelManager; + + private static final String EVENT_TOKEN_CHANGE = "registration"; + private static final String EVENT_TOKEN_ERROR = "registrationError"; + + public void load() { + notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + staticBridge = this.bridge; + if (lastMessage != null) { + fireNotification(lastMessage); + lastMessage = null; + } + notificationChannelManager = new NotificationChannelManager(getActivity(), notificationManager); + } + + @Override + protected void handleOnNewIntent(Intent data) { + super.handleOnNewIntent(data); + Bundle bundle = data.getExtras(); + if (bundle != null && bundle.containsKey("google.message_id")) { + JSObject notificationJson = new JSObject(); + JSObject dataObject = new JSObject(); + for (String key : bundle.keySet()) { + if (key.equals("google.message_id")) { + notificationJson.put("id", bundle.get(key)); + } else { + Object value = bundle.get(key); + String valueStr = (value != null) ? value.toString() : null; + dataObject.put(key, valueStr); + } + } + notificationJson.put("data", dataObject); + JSObject actionJson = new JSObject(); + actionJson.put("actionId", "tap"); + actionJson.put("notification", notificationJson); + notifyListeners("pushNotificationActionPerformed", actionJson, true); + } + } + + @PluginMethod + public void register(PluginCall call) { + FirebaseMessaging.getInstance().setAutoInitEnabled(true); + FirebaseInstanceId + .getInstance() + .getInstanceId() + .addOnSuccessListener( + getActivity(), + new OnSuccessListener() { + @Override + public void onSuccess(InstanceIdResult instanceIdResult) { + sendToken(instanceIdResult.getToken()); + } + } + ); + FirebaseInstanceId + .getInstance() + .getInstanceId() + .addOnFailureListener( + new OnFailureListener() { + public void onFailure(Exception e) { + sendError(e.getLocalizedMessage()); + } + } + ); + call.success(); + } + + @PluginMethod + public void getDeliveredNotifications(PluginCall call) { + JSArray notifications = new JSArray(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications(); + + for (StatusBarNotification notif : activeNotifications) { + JSObject jsNotif = new JSObject(); + + jsNotif.put("id", notif.getId()); + + Notification notification = notif.getNotification(); + if (notification != null) { + jsNotif.put("title", notification.extras.getCharSequence(Notification.EXTRA_TITLE)); + jsNotif.put("body", notification.extras.getCharSequence(Notification.EXTRA_TEXT)); + jsNotif.put("group", notification.getGroup()); + jsNotif.put("groupSummary", 0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY)); + + JSObject extras = new JSObject(); + + for (String key : notification.extras.keySet()) { + extras.put(key, notification.extras.get(key)); + } + + jsNotif.put("data", extras); + } + + notifications.put(jsNotif); + } + } + + JSObject result = new JSObject(); + result.put("notifications", notifications); + call.resolve(result); + } + + @PluginMethod + public void removeDeliveredNotifications(PluginCall call) { + JSArray notifications = call.getArray("notifications"); + + List ids = new ArrayList<>(); + try { + for (Object o : notifications.toList()) { + if (o instanceof JSONObject) { + JSObject notif = JSObject.fromJSONObject((JSONObject) o); + Integer id = notif.getInteger("id"); + ids.add(id); + } else { + call.reject("Expected notifications to be a list of notification objects"); + } + } + } catch (JSONException e) { + call.reject(e.getMessage()); + } + + for (int id : ids) { + notificationManager.cancel(id); + } + + call.resolve(); + } @PluginMethod - public void echo(PluginCall call) { - String value = call.getString("value"); + public void removeAllDeliveredNotifications(PluginCall call) { + notificationManager.cancelAll(); + call.success(); + } + + @PluginMethod + public void createChannel(PluginCall call) { + notificationChannelManager.createChannel(call); + } + + @PluginMethod + public void deleteChannel(PluginCall call) { + notificationChannelManager.deleteChannel(call); + } + + @PluginMethod + public void listChannels(PluginCall call) { + notificationChannelManager.listChannels(call); + } + + public void sendToken(String token) { + JSObject data = new JSObject(); + data.put("value", token); + notifyListeners(EVENT_TOKEN_CHANGE, data, true); + } + + public void sendError(String error) { + JSObject data = new JSObject(); + data.put("error", error); + notifyListeners(EVENT_TOKEN_ERROR, data, true); + } + + public static void onNewToken(String newToken) { + PushNotificationsPlugin pushPlugin = PushNotificationsPlugin.getPushNotificationsInstance(); + if (pushPlugin != null) { + pushPlugin.sendToken(newToken); + } + } + + public static void sendRemoteMessage(RemoteMessage remoteMessage) { + PushNotificationsPlugin pushPlugin = PushNotificationsPlugin.getPushNotificationsInstance(); + if (pushPlugin != null) { + pushPlugin.fireNotification(remoteMessage); + } else { + lastMessage = remoteMessage; + } + } + + public void fireNotification(RemoteMessage remoteMessage) { + JSObject remoteMessageData = new JSObject(); + + JSObject data = new JSObject(); + remoteMessageData.put("id", remoteMessage.getMessageId()); + for (String key : remoteMessage.getData().keySet()) { + Object value = remoteMessage.getData().get(key); + data.put(key, value); + } + remoteMessageData.put("data", data); + + RemoteMessage.Notification notification = remoteMessage.getNotification(); + if (notification != null) { + remoteMessageData.put("title", notification.getTitle()); + remoteMessageData.put("body", notification.getBody()); + remoteMessageData.put("click_action", notification.getClickAction()); + + Uri link = notification.getLink(); + if (link != null) { + remoteMessageData.put("link", link.toString()); + } + } + + notifyListeners("pushNotificationReceived", remoteMessageData, true); + } - JSObject ret = new JSObject(); - ret.put("value", implementation.echo(value)); - call.resolve(ret); + public static PushNotificationsPlugin getPushNotificationsInstance() { + if (staticBridge != null && staticBridge.getWebView() != null) { + PluginHandle handle = staticBridge.getPlugin("PushNotifications"); + if (handle == null) { + return null; + } + return (PushNotificationsPlugin) handle.getInstance(); + } + return null; } } diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index f2e8bb551..f5d7176c5 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -1,3 +1,153 @@ +import type { PermissionState, PluginListenerHandle } from '@capacitor/core'; + export interface PushNotificationsPlugin { - echo(options: { value: string }): Promise<{ value: string }>; + /** + * Register the app to receive push notifications. + * Will trigger registration event with the push token + * or registrationError if there was some problem. + * Doesn't prompt the user for notification permissions, use requestPermission() first. + */ + register(): Promise; + /** + * Returns the notifications that are visible on the notifications screen. + */ + getDeliveredNotifications(): Promise; + /** + * Removes the specified notifications from the notifications screen. + * @param delivered list of delivered notifications. + */ + removeDeliveredNotifications( + delivered: PushNotificationDeliveredList, + ): Promise; + /** + * Removes all the notifications from the notifications screen. + */ + removeAllDeliveredNotifications(): Promise; + /** + * On Android O or newer (SDK 26+) creates a notification channel. + * @param channel to create. + */ + createChannel(channel: Channel): Promise; + /** + * On Android O or newer (SDK 26+) deletes a notification channel. + * @param channel to delete. + */ + deleteChannel(channel: Channel): Promise; + /** + * On Android O or newer (SDK 26+) list the available notification channels. + */ + listChannels(): Promise; + + /** + * Check permission to receive push notifications. + * + * @since 1.0.0 + */ + checkPermissions(): Promise; + + /** + * Request permission to receive push notifications. + * + * @since 1.0.0 + */ + requestPermissions(): Promise; + + /** + * Event called when the push notification registration finished without problems. + * Provides the push notification token. + * @param eventName registration. + * @param listenerFunc callback with the push token. + */ + addListener( + eventName: 'registration', + listenerFunc: (token: PushNotificationToken) => void, + ): PluginListenerHandle; + /** + * Event called when the push notification registration finished with problems. + * Provides an error with the registration problem. + * @param eventName registrationError. + * @param listenerFunc callback with the registration error. + */ + addListener( + eventName: 'registrationError', + listenerFunc: (error: any) => void, + ): PluginListenerHandle; + /** + * Event called when the device receives a push notification. + * @param eventName pushNotificationReceived. + * @param listenerFunc callback with the received notification. + */ + addListener( + eventName: 'pushNotificationReceived', + listenerFunc: (notification: PushNotificationSchema) => void, + ): PluginListenerHandle; + /** + * Event called when an action is performed on a pusn notification. + * @param eventName pushNotificationActionPerformed. + * @param listenerFunc callback with the notification action. + */ + addListener( + eventName: 'pushNotificationActionPerformed', + listenerFunc: (notification: PushNotificationActionPerformed) => void, + ): PluginListenerHandle; + /** + * Remove all native listeners for this plugin. + */ + removeAllListeners(): void; +} + +export interface PushNotificationSchema { + title?: string; + subtitle?: string; + body?: string; + id: string; + badge?: number; + notification?: any; + data: any; + click_action?: string; + link?: string; + /** + * Android only: set the group identifier for notification grouping, like + * threadIdentifier on iOS. + */ + group?: string; + /** + * Android only: designate this notification as the summary for a group + * (should be used with the `group` property). + */ + groupSummary?: boolean; +} + +export interface PushNotificationActionPerformed { + actionId: string; + inputValue?: string; + notification: PushNotificationSchema; +} + +export interface PushNotificationToken { + value: string; +} + +export interface PushNotificationDeliveredList { + notifications: PushNotificationSchema[]; +} + +export interface Channel { + id: string; + name: string; + description?: string; + sound?: string; + importance: 1 | 2 | 3 | 4 | 5; + visibility?: -1 | 0 | 1; + lights?: boolean; + lightColor?: string; + vibration?: boolean; +} + +export interface ListChannelsResult { + channels: Channel[]; +} + +export interface PermissionStatus { + receive: PermissionState; } diff --git a/push-notifications/src/index.ts b/push-notifications/src/index.ts index fcaa29240..ab86e2aed 100644 --- a/push-notifications/src/index.ts +++ b/push-notifications/src/index.ts @@ -4,9 +4,7 @@ import type { PushNotificationsPlugin } from './definitions'; const PushNotifications = registerPlugin( 'PushNotifications', - { - web: () => import('./web').then(m => new m.PushNotificationsWeb()), - }, + {}, ); export { PushNotifications }; diff --git a/push-notifications/src/web.ts b/push-notifications/src/web.ts deleted file mode 100644 index b65a8d9c8..000000000 --- a/push-notifications/src/web.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { WebPlugin } from '@capacitor/core'; - -import type { PushNotificationsPlugin } from './definitions'; - -export class PushNotificationsWeb - extends WebPlugin - implements PushNotificationsPlugin { - async echo(options: { value: string }): Promise<{ value: string }> { - console.log('ECHO', options); - return options; - } -} From 54348c7d7099ad129857b09178cfa6c375b605fd Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Mon, 7 Dec 2020 13:11:53 -0800 Subject: [PATCH 06/36] remove echo test --- .../PluginTests/PushNotificationsPluginTests.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift b/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift index 2d0750d3d..f87d8508c 100644 --- a/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift +++ b/push-notifications/ios/PluginTests/PushNotificationsPluginTests.swift @@ -11,15 +11,4 @@ class PushNotificationsTests: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } - - func testEcho() { - // This is an example of a functional test case for a plugin. - // Use XCTAssert and related functions to verify your tests produce the correct results. - - let implementation = PushNotifications() - let value = "Hello, World!" - let result = implementation.echo(value) - - XCTAssertEqual(value, result) - } } From 6ea75f00f50cd4ad24319861d1c2e6b1fe1a2a67 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 16 Dec 2020 17:02:40 -0600 Subject: [PATCH 07/36] Basic working push notification code from v2 plugin --- .../CapacitorPushNotifications.podspec | 2 + .../ios/Plugin.xcodeproj/project.pbxproj | 24 +++ .../Plugin/PushNotificationsDelegate.swift | 73 +++++++++ .../ios/Plugin/PushNotificationsPlugin.m | 3 + .../ios/Plugin/PushNotificationsPlugin.swift | 153 ++++++++++++++++-- push-notifications/ios/Podfile | 3 +- 6 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 push-notifications/ios/Plugin/PushNotificationsDelegate.swift diff --git a/push-notifications/CapacitorPushNotifications.podspec b/push-notifications/CapacitorPushNotifications.podspec index efe3f3782..fadda07c6 100644 --- a/push-notifications/CapacitorPushNotifications.podspec +++ b/push-notifications/CapacitorPushNotifications.podspec @@ -13,5 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '11.0' s.dependency 'Capacitor' + s.dependency 'Firebase/Messaging', '~> 7.3' + s.static_framework = true s.swift_version = '5.1' end diff --git a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj index c27768d6f..f92051219 100644 --- a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj +++ b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFFA52020D75100D50D53 /* Capacitor.framework */; }; 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */; }; 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */; }; + CFA171D7258422F200F2DED2 /* PushNotificationsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -43,6 +44,8 @@ 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.debug.xcconfig"; sourceTree = ""; }; 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.release.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.release.xcconfig"; sourceTree = ""; }; 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.debug.xcconfig"; sourceTree = ""; }; + CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsDelegate.swift; sourceTree = ""; }; + CFDB66F62587D6E400732657 /* FirebaseMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.release.xcconfig"; sourceTree = ""; }; F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -97,6 +100,7 @@ 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */, 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */, 50ADFF8C201F53D600D50D53 /* Info.plist */, + CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */, ); path = Plugin; sourceTree = ""; @@ -124,6 +128,7 @@ A797B9EFA3DCEFEA1FBB66A9 /* Frameworks */ = { isa = PBXGroup; children = ( + CFDB66F62587D6E400732657 /* FirebaseMessaging.framework */, 50ADFFA52020D75100D50D53 /* Capacitor.framework */, 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */, F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */, @@ -270,11 +275,29 @@ "${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Capacitor/Capacitor.framework", "${BUILT_PRODUCTS_DIR}/CapacitorCordova/Cordova.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstanceID/FirebaseInstanceID.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseMessaging/FirebaseMessaging.framework", + "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Capacitor.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cordova.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstanceID.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseMessaging.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -309,6 +332,7 @@ 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */, 2F98D68224C9AAE500613A4C /* PushNotifications.swift in Sources */, 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */, + CFA171D7258422F200F2DED2 /* PushNotificationsDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift new file mode 100644 index 000000000..3ac95ddf8 --- /dev/null +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -0,0 +1,73 @@ +import Capacitor +import UserNotifications + +public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { + public var plugin: CAPPlugin? + var notificationRequestLookup = [String: JSObject]() + + public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + completion?(granted, error) + } + } + + public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + completion?(settings.authorizationStatus) + } + } + + public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions { + let notificationData = makeNotificationRequestJSObject(notification.request) + self.plugin?.notifyListeners("received", data: notificationData) + + if let options = notificationRequestLookup[notification.request.identifier] { + let silent = options["silent"] as? Bool ?? false + + if silent { + return UNNotificationPresentationOptions.init(rawValue: 0) + } + } + + return [.badge, .sound, .alert] + } + + public func didReceive(response: UNNotificationResponse) { + print("did receive notification in the background") + print("\(response)") + var data = JSObject() + + let originalNotificationRequest = response.notification.request + let actionId = response.actionIdentifier + + if actionId == UNNotificationDefaultActionIdentifier { + data["actionId"] = "tap" + } else if actionId == UNNotificationDismissActionIdentifier { + data["actionId"] = "dismiss" + } else { + data["actionId"] = actionId + } + + if let inputType = response as? UNTextInputNotificationResponse { + data["inputValue"] = inputType.userText + } + + data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest) + + self.plugin?.notifyListeners("actionPerformed", data: data, retainUntilConsumed: true) + + } + + func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject { + let notificationRequest = notificationRequestLookup[request.identifier] ?? [:] + return [ + "id": request.identifier, + "title": request.content.title, + "sound": notificationRequest["sound"] ?? "", + "body": request.content.body, + "extra": request.content.userInfo as? JSObject ?? [:], + "actionTypeId": request.content.categoryIdentifier, + "attachments": notificationRequest["attachments"] ?? [] + ] + } +} diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.m b/push-notifications/ios/Plugin/PushNotificationsPlugin.m index 2a2caeb12..6e641d900 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.m +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.m @@ -5,4 +5,7 @@ // each method the plugin supports using the CAP_PLUGIN_METHOD macro. CAP_PLUGIN(PushNotificationsPlugin, "PushNotifications", CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise); ) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index c2af8ede9..9ccd3ef53 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -1,18 +1,153 @@ import Foundation import Capacitor +import FirebaseCore +import FirebaseMessaging +import UserNotifications + + +enum PushNotificationError: Error { + case tokenParsingFailed +} + +enum LocationPermissions: String { + case prompt = "prompt" + case denied = "denied" + case granted = "granted" +} -/** - * Please read the Capacitor iOS Plugin Development Guide - * here: https://capacitorjs.com/docs/plugins/ios - */ @objc(PushNotificationsPlugin) public class PushNotificationsPlugin: CAPPlugin { - private let implementation = PushNotifications() + private let notificationDelegateHandler = PushNotificationsDelegate() + + // Local list of notification id -> JSObject for storing options + // between notification requets + var notificationRequestLookup = [String:JSObject]() + + public override func load() { + FirebaseApp.configure() + self.bridge?.notificationRouter.pushNotificationHandler = self.notificationDelegateHandler + self.notificationDelegateHandler.plugin = self + + NotificationCenter.default.addObserver(self, selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), name: Notification.Name(Notification.Name.capacitorDidRegisterForRemoteNotifications.self.rawValue), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), name: Notification.Name(Notification.Name.capacitorDidFailToRegisterForRemoteNotifications.self.rawValue), object: nil) + } + + /** + * Register for push notifications + */ + @objc func register(_ call: CAPPluginCall) { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + call.resolve() + } + + /** + * Request notification permission + */ + @objc public override func requestPermissions(_ call: CAPPluginCall) { + self.notificationDelegateHandler.requestPermissions { granted, error in + guard error == nil else { + call.reject(error!.localizedDescription) + return + } + + var result: LocationPermissions = .denied + + if granted { + result = .granted + } + + call.resolve(["receive": result.rawValue]) + } + } + + @objc override public func checkPermissions(_ call: CAPPluginCall) { + self.notificationDelegateHandler.checkPermissions { status in + var result: LocationPermissions = .prompt + + switch status { + case .notDetermined: + result = .prompt + case .denied: + result = .denied + case .ephemeral, .authorized, .provisional: + result = .granted + default: + result = .prompt + } + + call.resolve(["receive": result.rawValue]) + } + } + + /** + * Get notifications in Notification Center + */ + @objc func getDeliveredNotifications(_ call: CAPPluginCall) { + + + } + + /** + * Remove specified notifications from Notification Center + */ + @objc func removeDeliveredNotifications(_ call: CAPPluginCall) { + // TODO + call.unimplemented("not implemented in swift") + } + + /** + * Remove all notifications from Notification Center + */ + @objc func removeAllDeliveredNotifications(_ call: CAPPluginCall) { + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + DispatchQueue.main.async(execute: { + UIApplication.shared.applicationIconBadgeNumber = 0 + }) + call.resolve() + } - @objc func echo(_ call: CAPPluginCall) { - let value = call.getString("value") ?? "" - call.resolve([ - "value": implementation.echo(value) + + @objc func createChannel(_ call: CAPPluginCall) { + call.unimplemented() + } + + @objc func deleteChannel(_ call: CAPPluginCall) { + call.unimplemented() + } + + @objc func listChannels(_ call: CAPPluginCall) { + call.unimplemented() + } + + @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification){ + print("calling did register for notifications") + if let deviceToken = notification.object as? Data { + let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) + Messaging.messaging().apnsToken = deviceToken + notifyListeners("registration", data:[ + "value": deviceTokenString + ]) + } else if let stringToken = notification.object as? String { + Messaging.messaging().apnsToken = stringToken.data(using: .utf8) + notifyListeners("registration", data:[ + "value": stringToken + ]) + } else { + notifyListeners("registrationError", data: [ + "error": PushNotificationError.tokenParsingFailed.localizedDescription + ]) + } + } + + @objc public func didFailToRegisterForRemoteNotificationsWithError(notification: NSNotification){ + print("calling FAILED register for notifications") + guard let error = notification.object as? Error else { + return + } + notifyListeners("registrationError", data:[ + "error": error.localizedDescription ]) } } diff --git a/push-notifications/ios/Podfile b/push-notifications/ios/Podfile index 350751435..d3d15b83b 100644 --- a/push-notifications/ios/Podfile +++ b/push-notifications/ios/Podfile @@ -4,11 +4,12 @@ def capacitor_pods # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! pod 'Capacitor', :path => '../node_modules/@capacitor/ios' - pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' end target 'Plugin' do capacitor_pods + pod 'Firebase/Messaging', '~> 7.3' end target 'PluginTests' do From 524474e12bbb5a85cbe6eaca6f3cba6467b89650 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 18 Dec 2020 10:18:37 -0600 Subject: [PATCH 08/36] Implementing remaining missing APIs --- .../Plugin/PushNotificationsDelegate.swift | 4 ++-- .../ios/Plugin/PushNotificationsPlugin.m | 3 +++ .../ios/Plugin/PushNotificationsPlugin.swift | 20 +++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift index 3ac95ddf8..7b62a696f 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -19,7 +19,7 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions { let notificationData = makeNotificationRequestJSObject(notification.request) - self.plugin?.notifyListeners("received", data: notificationData) + self.plugin?.notifyListeners("pushNotificationReceived", data: notificationData) if let options = notificationRequestLookup[notification.request.identifier] { let silent = options["silent"] as? Bool ?? false @@ -54,7 +54,7 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest) - self.plugin?.notifyListeners("actionPerformed", data: data, retainUntilConsumed: true) + self.plugin?.notifyListeners("pushNotificationActionPerformed", data: data, retainUntilConsumed: true) } diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.m b/push-notifications/ios/Plugin/PushNotificationsPlugin.m index 6e641d900..01b8df49e 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.m +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.m @@ -8,4 +8,7 @@ CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(getDeliveredNotifications, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise);; + CAP_PLUGIN_METHOD(removeDeliveredNotifications, CAPPluginReturnPromise); ) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index 9ccd3ef53..eb22cf493 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -85,16 +85,28 @@ public class PushNotificationsPlugin: CAPPlugin { * Get notifications in Notification Center */ @objc func getDeliveredNotifications(_ call: CAPPluginCall) { - - + UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { (notifications) in + let ret = notifications.map({ (notification) -> [String:Any] in + return self.notificationDelegateHandler.makeNotificationRequestJSObject(notification.request) + }) + call.resolve([ + "notifications": ret + ]) + }) } /** * Remove specified notifications from Notification Center */ @objc func removeDeliveredNotifications(_ call: CAPPluginCall) { - // TODO - call.unimplemented("not implemented in swift") + guard let notifications = call.getArray("notifications", JSObject.self) else { + call.reject("Must supply notifications to remove") + return + } + + let ids = notifications.map { $0["id"] as? String ?? "" } + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids) + call.resolve() } /** From 45cdb3bf0ed6b30806c07a10a0e0918612248d2e Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 18 Dec 2020 10:28:46 -0600 Subject: [PATCH 09/36] Code cleanup --- .../ios/Plugin/PushNotificationsDelegate.swift | 2 -- .../ios/Plugin/PushNotificationsPlugin.m | 4 +++- .../ios/Plugin/PushNotificationsPlugin.swift | 16 +++++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift index 7b62a696f..d065f4692 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -33,8 +33,6 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { } public func didReceive(response: UNNotificationResponse) { - print("did receive notification in the background") - print("\(response)") var data = JSObject() let originalNotificationRequest = response.notification.request diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.m b/push-notifications/ios/Plugin/PushNotificationsPlugin.m index 01b8df49e..6a0a3bf3b 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.m +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.m @@ -4,11 +4,13 @@ // Define the plugin using the CAP_PLUGIN Macro, and // each method the plugin supports using the CAP_PLUGIN_METHOD macro. CAP_PLUGIN(PushNotificationsPlugin, "PushNotifications", - CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(getDeliveredNotifications, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise);; CAP_PLUGIN_METHOD(removeDeliveredNotifications, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(deleteChannel, CAPPluginReturnPromise); ) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index eb22cf493..41cfe2604 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -19,10 +19,6 @@ enum LocationPermissions: String { public class PushNotificationsPlugin: CAPPlugin { private let notificationDelegateHandler = PushNotificationsDelegate() - // Local list of notification id -> JSObject for storing options - // between notification requets - var notificationRequestLookup = [String:JSObject]() - public override func load() { FirebaseApp.configure() self.bridge?.notificationRouter.pushNotificationHandler = self.notificationDelegateHandler @@ -62,6 +58,10 @@ public class PushNotificationsPlugin: CAPPlugin { } } + + /** + * Check notification permission + */ @objc override public func checkPermissions(_ call: CAPPluginCall) { self.notificationDelegateHandler.checkPermissions { status in var result: LocationPermissions = .prompt @@ -122,19 +122,18 @@ public class PushNotificationsPlugin: CAPPlugin { @objc func createChannel(_ call: CAPPluginCall) { - call.unimplemented() + call.unavailable("Not available on iOS") } @objc func deleteChannel(_ call: CAPPluginCall) { - call.unimplemented() + call.unavailable("Not available on iOS") } @objc func listChannels(_ call: CAPPluginCall) { - call.unimplemented() + call.unavailable("Not available on iOS") } @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification){ - print("calling did register for notifications") if let deviceToken = notification.object as? Data { let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) Messaging.messaging().apnsToken = deviceToken @@ -154,7 +153,6 @@ public class PushNotificationsPlugin: CAPPlugin { } @objc public func didFailToRegisterForRemoteNotificationsWithError(notification: NSNotification){ - print("calling FAILED register for notifications") guard let error = notification.object as? Error else { return } From 2566eb13d2ed388e5064f3d25461ce046cc7af7f Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 21 Dec 2020 17:47:54 -0600 Subject: [PATCH 10/36] handling capacitor.config.json presentationOptions --- .../Plugin/PushNotificationsDelegate.swift | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift index d065f4692..58f02840b 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -1,43 +1,67 @@ import Capacitor import UserNotifications -public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { +public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { public var plugin: CAPPlugin? var notificationRequestLookup = [String: JSObject]() - + public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in completion?(granted, error) } } - + public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) { UNUserNotificationCenter.current().getNotificationSettings { settings in completion?(settings.authorizationStatus) } } - + public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions { - let notificationData = makeNotificationRequestJSObject(notification.request) + let notificationData = makeNotificationRequestJSObject(notification.request) self.plugin?.notifyListeners("pushNotificationReceived", data: notificationData) - + if let options = notificationRequestLookup[notification.request.identifier] { let silent = options["silent"] as? Bool ?? false - + if silent { return UNNotificationPresentationOptions.init(rawValue: 0) } } - + + if let optionsArray = self.plugin?.getConfigValue("presentationOptions") as? [String] { + var presentationOptions = UNNotificationPresentationOptions.init() + + optionsArray.forEach { option in + switch option { + case "alert": + presentationOptions.insert(.alert) + break + case "badge": + presentationOptions.insert(.badge)x + break + case "sound": + presentationOptions.insert(.sound) + break + default: + print("Unrecogizned presentation option: \(option)") + break + } + + } + + return presentationOptions + } + return [.badge, .sound, .alert] } - + public func didReceive(response: UNNotificationResponse) { var data = JSObject() - + let originalNotificationRequest = response.notification.request let actionId = response.actionIdentifier - + if actionId == UNNotificationDefaultActionIdentifier { data["actionId"] = "tap" } else if actionId == UNNotificationDismissActionIdentifier { @@ -45,17 +69,17 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { } else { data["actionId"] = actionId } - + if let inputType = response as? UNTextInputNotificationResponse { data["inputValue"] = inputType.userText } - + data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest) - + self.plugin?.notifyListeners("pushNotificationActionPerformed", data: data, retainUntilConsumed: true) - + } - + func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject { let notificationRequest = notificationRequestLookup[request.identifier] ?? [:] return [ From 72d9fe2cdc585e9694fe15fc82dd56160b5ba475 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 23 Dec 2020 17:10:04 -0600 Subject: [PATCH 11/36] [android] Adding foreground notification handling --- .../PushNotificationsPlugin.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java index 089ab83c5..f74669ea3 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java @@ -8,6 +8,7 @@ import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; +import androidx.annotation.NonNull; import com.getcapacitor.Bridge; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; @@ -22,6 +23,7 @@ import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import java.util.ArrayList; import java.util.List; @@ -34,18 +36,40 @@ public class PushNotificationsPlugin extends Plugin { public static Bridge staticBridge = null; public static RemoteMessage lastMessage = null; public NotificationManager notificationManager; + public MessagingService firebaseMessagingService; private NotificationChannelManager notificationChannelManager; private static final String EVENT_TOKEN_CHANGE = "registration"; private static final String EVENT_TOKEN_ERROR = "registrationError"; + public static class MessagingService extends FirebaseMessagingService { + + @Override + public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { + super.onMessageReceived(remoteMessage); + PushNotificationsPlugin pushPlugin = PushNotificationsPlugin.getPushNotificationsInstance(); + if (pushPlugin != null) { + pushPlugin.fireNotification(remoteMessage); + } + } + + @Override + public void onNewToken(@NonNull String s) { + super.onNewToken(s); + PushNotificationsPlugin.onNewToken(s); + } + } + public void load() { notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + firebaseMessagingService = new MessagingService(); + staticBridge = this.bridge; if (lastMessage != null) { fireNotification(lastMessage); lastMessage = null; } + notificationChannelManager = new NotificationChannelManager(getActivity(), notificationManager); } @@ -98,7 +122,7 @@ public void onFailure(Exception e) { } } ); - call.success(); + call.resolve(); } @PluginMethod @@ -166,7 +190,7 @@ public void removeDeliveredNotifications(PluginCall call) { @PluginMethod public void removeAllDeliveredNotifications(PluginCall call) { notificationManager.cancelAll(); - call.success(); + call.resolve(); } @PluginMethod From cc699682495bbcbe437a59adedf4a7df33fe6e1c Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Mon, 28 Dec 2020 10:19:15 -0600 Subject: [PATCH 12/36] file linting --- .../Plugin/PushNotificationsDelegate.swift | 2 +- .../ios/Plugin/PushNotificationsPlugin.m | 2 +- .../ios/Plugin/PushNotificationsPlugin.swift | 97 +++++++++++-------- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift index 58f02840b..f40c2d008 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -38,7 +38,7 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { presentationOptions.insert(.alert) break case "badge": - presentationOptions.insert(.badge)x + presentationOptions.insert(.badge) break case "sound": presentationOptions.insert(.sound) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.m b/push-notifications/ios/Plugin/PushNotificationsPlugin.m index 6a0a3bf3b..86591728c 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.m +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.m @@ -8,7 +8,7 @@ CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(getDeliveredNotifications, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise);; + CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(removeDeliveredNotifications, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise); diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index 41cfe2604..49afb9b0a 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -4,9 +4,9 @@ import FirebaseCore import FirebaseMessaging import UserNotifications - enum PushNotificationError: Error { case tokenParsingFailed + case tokenRegistrationFailed } enum LocationPermissions: String { @@ -18,16 +18,17 @@ enum LocationPermissions: String { @objc(PushNotificationsPlugin) public class PushNotificationsPlugin: CAPPlugin { private let notificationDelegateHandler = PushNotificationsDelegate() - - public override func load() { + private var appDelegateRegistrationCalled: Bool = false + + override public func load() { FirebaseApp.configure() self.bridge?.notificationRouter.pushNotificationHandler = self.notificationDelegateHandler self.notificationDelegateHandler.plugin = self - + NotificationCenter.default.addObserver(self, selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), name: Notification.Name(Notification.Name.capacitorDidRegisterForRemoteNotifications.self.rawValue), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), name: Notification.Name(Notification.Name.capacitorDidFailToRegisterForRemoteNotifications.self.rawValue), object: nil) } - + /** * Register for push notifications */ @@ -37,35 +38,34 @@ public class PushNotificationsPlugin: CAPPlugin { } call.resolve() } - + /** * Request notification permission */ - @objc public override func requestPermissions(_ call: CAPPluginCall) { + @objc override public func requestPermissions(_ call: CAPPluginCall) { self.notificationDelegateHandler.requestPermissions { granted, error in guard error == nil else { call.reject(error!.localizedDescription) return } - + var result: LocationPermissions = .denied - + if granted { result = .granted } - + call.resolve(["receive": result.rawValue]) } } - - + /** * Check notification permission */ @objc override public func checkPermissions(_ call: CAPPluginCall) { self.notificationDelegateHandler.checkPermissions { status in var result: LocationPermissions = .prompt - + switch status { case .notDetermined: result = .prompt @@ -76,17 +76,21 @@ public class PushNotificationsPlugin: CAPPlugin { default: result = .prompt } - + call.resolve(["receive": result.rawValue]) } } - + /** * Get notifications in Notification Center */ @objc func getDeliveredNotifications(_ call: CAPPluginCall) { + if !appDelegateRegistrationCalled { + call.reject("event capacitorDidRegisterForRemoteNotifications not called. Visit https://capacitorjs.com/docs/apis/push-notifications for more information") + return + } UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { (notifications) in - let ret = notifications.map({ (notification) -> [String:Any] in + let ret = notifications.map({ (notification) -> [String: Any] in return self.notificationDelegateHandler.makeNotificationRequestJSObject(notification.request) }) call.resolve([ @@ -94,25 +98,33 @@ public class PushNotificationsPlugin: CAPPlugin { ]) }) } - + /** * Remove specified notifications from Notification Center */ @objc func removeDeliveredNotifications(_ call: CAPPluginCall) { + if !appDelegateRegistrationCalled { + call.reject("event capacitorDidRegisterForRemoteNotifications not called. Visit https://capacitorjs.com/docs/apis/push-notifications for more information") + return + } guard let notifications = call.getArray("notifications", JSObject.self) else { call.reject("Must supply notifications to remove") return } - + let ids = notifications.map { $0["id"] as? String ?? "" } UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids) call.resolve() } - + /** * Remove all notifications from Notification Center */ @objc func removeAllDeliveredNotifications(_ call: CAPPluginCall) { + if !appDelegateRegistrationCalled { + call.reject("event capacitorDidRegisterForRemoteNotifications not called. Visit https://capacitorjs.com/docs/apis/push-notifications for more information") + return + } UNUserNotificationCenter.current().removeAllDeliveredNotifications() DispatchQueue.main.async(execute: { UIApplication.shared.applicationIconBadgeNumber = 0 @@ -120,44 +132,45 @@ public class PushNotificationsPlugin: CAPPlugin { call.resolve() } - @objc func createChannel(_ call: CAPPluginCall) { - call.unavailable("Not available on iOS") + call.unimplemented("Not available on iOS") } - + @objc func deleteChannel(_ call: CAPPluginCall) { - call.unavailable("Not available on iOS") + call.unimplemented("Not available on iOS") } - + @objc func listChannels(_ call: CAPPluginCall) { - call.unavailable("Not available on iOS") + call.unimplemented("Not available on iOS") } - - @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification){ + + @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification) { + appDelegateRegistrationCalled = true if let deviceToken = notification.object as? Data { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) + let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) Messaging.messaging().apnsToken = deviceToken - notifyListeners("registration", data:[ - "value": deviceTokenString - ]) + notifyListeners("registration", data: [ + "value": deviceTokenString + ]) } else if let stringToken = notification.object as? String { Messaging.messaging().apnsToken = stringToken.data(using: .utf8) - notifyListeners("registration", data:[ - "value": stringToken - ]) + notifyListeners("registration", data: [ + "value": stringToken + ]) } else { - notifyListeners("registrationError", data: [ - "error": PushNotificationError.tokenParsingFailed.localizedDescription - ]) + notifyListeners("registrationError", data: [ + "error": PushNotificationError.tokenParsingFailed.localizedDescription + ]) } } - - @objc public func didFailToRegisterForRemoteNotificationsWithError(notification: NSNotification){ + + @objc public func didFailToRegisterForRemoteNotificationsWithError(notification: NSNotification) { + appDelegateRegistrationCalled = true guard let error = notification.object as? Error else { - return + return } - notifyListeners("registrationError", data:[ - "error": error.localizedDescription + notifyListeners("registrationError", data: [ + "error": error.localizedDescription ]) } } From 4baaa7e92fc605c86ef2193bcc06394ee41381e3 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Tue, 29 Dec 2020 21:31:32 -0600 Subject: [PATCH 13/36] [android] Moving Firebase MESSAGING_EVENT action into plugin --- .../android/src/main/AndroidManifest.xml | 8 ++++++++ .../pushnotifications/MessagingService.java | 20 +++++++++++++++++++ .../PushNotificationsPlugin.java | 20 ------------------- 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java diff --git a/push-notifications/android/src/main/AndroidManifest.xml b/push-notifications/android/src/main/AndroidManifest.xml index 0259c07d0..fd2bc3f4d 100644 --- a/push-notifications/android/src/main/AndroidManifest.xml +++ b/push-notifications/android/src/main/AndroidManifest.xml @@ -1,3 +1,11 @@ + + + + + + + + diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java new file mode 100644 index 000000000..285e5872b --- /dev/null +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java @@ -0,0 +1,20 @@ +package com.capacitorjs.plugins.pushnotifications; + +import androidx.annotation.NonNull; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +public class MessagingService extends FirebaseMessagingService { + @Override + public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { + super.onMessageReceived(remoteMessage); + PushNotificationsPlugin.sendRemoteMessage(remoteMessage); + } + + @Override + public void onNewToken(@NonNull String s) { + super.onNewToken(s); + PushNotificationsPlugin.onNewToken(s); + } +} diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java index f74669ea3..019534454 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/PushNotificationsPlugin.java @@ -8,7 +8,6 @@ import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; -import androidx.annotation.NonNull; import com.getcapacitor.Bridge; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; @@ -23,7 +22,6 @@ import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.InstanceIdResult; import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import java.util.ArrayList; import java.util.List; @@ -42,24 +40,6 @@ public class PushNotificationsPlugin extends Plugin { private static final String EVENT_TOKEN_CHANGE = "registration"; private static final String EVENT_TOKEN_ERROR = "registrationError"; - public static class MessagingService extends FirebaseMessagingService { - - @Override - public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { - super.onMessageReceived(remoteMessage); - PushNotificationsPlugin pushPlugin = PushNotificationsPlugin.getPushNotificationsInstance(); - if (pushPlugin != null) { - pushPlugin.fireNotification(remoteMessage); - } - } - - @Override - public void onNewToken(@NonNull String s) { - super.onNewToken(s); - PushNotificationsPlugin.onNewToken(s); - } - } - public void load() { notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); firebaseMessagingService = new MessagingService(); From 4b90267055fe02332d98362602f3d40516af66fd Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 09:34:08 -0600 Subject: [PATCH 14/36] [android] removing deprecated calls --- .../pushnotifications/NotificationChannelManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java index 72ef52bc0..0551094eb 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java @@ -47,7 +47,7 @@ public void createChannel(PluginCall call) { channel.put(CHANNEL_USE_LIGHTS, call.getBoolean(CHANNEL_USE_LIGHTS, false)); channel.put(CHANNEL_LIGHT_COLOR, call.getString(CHANNEL_LIGHT_COLOR, null)); createChannel(channel); - call.success(); + call.resolve(); } else { call.unavailable(); } @@ -92,7 +92,7 @@ public void deleteChannel(PluginCall call) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { String channelId = call.getString("id"); notificationManager.deleteNotificationChannel(channelId); - call.success(); + call.resolve(); } else { call.unavailable(); } @@ -119,7 +119,7 @@ public void listChannels(PluginCall call) { } JSObject result = new JSObject(); result.put("channels", channels); - call.success(result); + call.resolve(result); } else { call.unavailable(); } From 848d421d13131b39366aa2ba76023ac75b7c472f Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 14:12:54 -0600 Subject: [PATCH 15/36] formatting/linting --- .../pushnotifications/MessagingService.java | 2 +- .../Plugin/PushNotificationsDelegate.swift | 6 +---- .../ios/Plugin/PushNotificationsPlugin.swift | 24 ++++++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java index 285e5872b..55c4b2db5 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/MessagingService.java @@ -1,11 +1,11 @@ package com.capacitorjs.plugins.pushnotifications; import androidx.annotation.NonNull; - import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; public class MessagingService extends FirebaseMessagingService { + @Override public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { super.onMessageReceived(remoteMessage); diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift index f40c2d008..aa11e9b3b 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsDelegate.swift @@ -36,18 +36,14 @@ public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { switch option { case "alert": presentationOptions.insert(.alert) - break case "badge": presentationOptions.insert(.badge) - break + case "sound": presentationOptions.insert(.sound) - break default: print("Unrecogizned presentation option: \(option)") - break } - } return presentationOptions diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index 49afb9b0a..710de1340 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -10,9 +10,9 @@ enum PushNotificationError: Error { } enum LocationPermissions: String { - case prompt = "prompt" - case denied = "denied" - case granted = "granted" + case prompt + case denied + case granted } @objc(PushNotificationsPlugin) @@ -25,8 +25,15 @@ public class PushNotificationsPlugin: CAPPlugin { self.bridge?.notificationRouter.pushNotificationHandler = self.notificationDelegateHandler self.notificationDelegateHandler.plugin = self - NotificationCenter.default.addObserver(self, selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), name: Notification.Name(Notification.Name.capacitorDidRegisterForRemoteNotifications.self.rawValue), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), name: Notification.Name(Notification.Name.capacitorDidFailToRegisterForRemoteNotifications.self.rawValue), object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), + name: Notification.Name(Notification.Name.capacitorDidRegisterForRemoteNotifications.self.rawValue), + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), + name: Notification.Name(Notification.Name.capacitorDidFailToRegisterForRemoteNotifications.self.rawValue), + object: nil) } /** @@ -45,7 +52,12 @@ public class PushNotificationsPlugin: CAPPlugin { @objc override public func requestPermissions(_ call: CAPPluginCall) { self.notificationDelegateHandler.requestPermissions { granted, error in guard error == nil else { - call.reject(error!.localizedDescription) + if let err = error { + call.reject(err.localizedDescription) + return + } + + call.reject("unknown error in permissions request") return } From 39f8d84a1d93b391c2aa529b73156b3cf2c3f1da Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 14:40:52 -0600 Subject: [PATCH 16/36] [iOS] increasing minimum iOS version in Podfile --- push-notifications/ios/Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/push-notifications/ios/Podfile b/push-notifications/ios/Podfile index d3d15b83b..0a4549460 100644 --- a/push-notifications/ios/Podfile +++ b/push-notifications/ios/Podfile @@ -1,4 +1,4 @@ -platform :ios, '11.0' +platform :ios, '12.0' def capacitor_pods # Comment the next line if you're not using Swift and don't want to use dynamic frameworks From 38e3705c7101e561188c860d6a04718d6f73598e Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 14:58:23 -0600 Subject: [PATCH 17/36] Removing unused code, removing jest --- .../ios/Plugin.xcodeproj/project.pbxproj | 4 ---- .../ios/Plugin/PushNotifications.swift | 7 ------- .../ios/Plugin/PushNotificationsPlugin.swift | 2 +- push-notifications/package.json | 16 ++++++---------- 4 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 push-notifications/ios/Plugin/PushNotifications.swift diff --git a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj index f92051219..cc3f929e0 100644 --- a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj +++ b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */; }; 20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */; }; - 2F98D68224C9AAE500613A4C /* PushNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F98D68124C9AAE400613A4C /* PushNotifications.swift */; }; 50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFF88201F53D600D50D53 /* Plugin.framework */; }; 50ADFF97201F53D600D50D53 /* PushNotificationsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFF96201F53D600D50D53 /* PushNotificationsPluginTests.swift */; }; 50ADFF99201F53D600D50D53 /* PushNotificationsPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -30,7 +29,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 2F98D68124C9AAE400613A4C /* PushNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotifications.swift; sourceTree = ""; }; 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50ADFF88201F53D600D50D53 /* Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PushNotificationsPlugin.h; sourceTree = ""; }; @@ -96,7 +94,6 @@ isa = PBXGroup; children = ( 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */, - 2F98D68124C9AAE400613A4C /* PushNotifications.swift */, 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */, 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */, 50ADFF8C201F53D600D50D53 /* Info.plist */, @@ -330,7 +327,6 @@ buildActionMask = 2147483647; files = ( 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */, - 2F98D68224C9AAE500613A4C /* PushNotifications.swift in Sources */, 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */, CFA171D7258422F200F2DED2 /* PushNotificationsDelegate.swift in Sources */, ); diff --git a/push-notifications/ios/Plugin/PushNotifications.swift b/push-notifications/ios/Plugin/PushNotifications.swift deleted file mode 100644 index cf71feb22..000000000 --- a/push-notifications/ios/Plugin/PushNotifications.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -@objc public class PushNotifications: NSObject { - @objc public func echo(_ value: String) -> String { - return value - } -} diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index 710de1340..ddac18b8e 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -85,7 +85,7 @@ public class PushNotificationsPlugin: CAPPlugin { result = .denied case .ephemeral, .authorized, .provisional: result = .granted - default: + @unknown default: result = .prompt } diff --git a/push-notifications/package.json b/push-notifications/package.json index a0e2e54ba..f5b5ba3c4 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -40,26 +40,22 @@ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.js", "clean": "rimraf ./dist", "watch": "tsc --watch", - "prepublishOnly": "npm run build", - "test": "jest" + "prepublishOnly": "npm run build" }, "devDependencies": { - "@capacitor/android": "^3.0.0-alpha.7", - "@capacitor/core": "^3.0.0-alpha.7", + "@capacitor/android": "^3.0.0-alpha.11", + "@capacitor/core": "^3.0.0-alpha.9", "@capacitor/docgen": "^0.0.10", - "@capacitor/ios": "^3.0.0-alpha.7", + "@capacitor/ios": "^3.0.0-alpha.11", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^1.0.1", "@ionic/swiftlint-config": "^1.1.2", - "@types/jest": "^26.0.15", "eslint": "^7.11.0", - "jest": "^26.6.3", "prettier": "~2.2.0", "prettier-plugin-java": "~1.0.0", "rimraf": "^3.0.2", "rollup": "^2.32.0", "swiftlint": "^1.0.1", - "ts-jest": "^26.4.4", "typescript": "~4.0.3" }, "peerDependencies": { @@ -78,7 +74,7 @@ "src": "android" } }, - "jest": { - "preset": "ts-jest" + "publishConfig": { + "access": "public" } } From dc5b193063a82573862b27a6ab45e2ebc0377879 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 15:02:04 -0600 Subject: [PATCH 18/36] bumping target SDK in android build.gradle --- push-notifications/android/build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/push-notifications/android/build.gradle b/push-notifications/android/build.gradle index 17237e5cc..0848ff252 100644 --- a/push-notifications/android/build.gradle +++ b/push-notifications/android/build.gradle @@ -1,9 +1,9 @@ ext { - junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12' + junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12' androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.1.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0' - firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '20.1.2' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0' + firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '20.1.2' } buildscript { @@ -19,10 +19,10 @@ buildscript { apply plugin: 'com.android.library' android { - compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 29 + compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 30 defaultConfig { minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 21 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 29 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From 7608c25283eb20104ec268c8a554ca7d53c61dfb Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 15:05:02 -0600 Subject: [PATCH 19/36] Renaming LocationPermissions to PushNotificationsPermissions --- push-notifications/ios/Plugin/PushNotificationsPlugin.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index ddac18b8e..be50c968e 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -9,7 +9,7 @@ enum PushNotificationError: Error { case tokenRegistrationFailed } -enum LocationPermissions: String { +enum PushNotificationsPermissions: String { case prompt case denied case granted @@ -61,7 +61,7 @@ public class PushNotificationsPlugin: CAPPlugin { return } - var result: LocationPermissions = .denied + var result: PushNotificationsPermissions = .denied if granted { result = .granted @@ -76,7 +76,7 @@ public class PushNotificationsPlugin: CAPPlugin { */ @objc override public func checkPermissions(_ call: CAPPluginCall) { self.notificationDelegateHandler.checkPermissions { status in - var result: LocationPermissions = .prompt + var result: PushNotificationsPermissions = .prompt switch status { case .notDetermined: From d634aabff5ff4baafa103cd0d05ad0481c0485dc Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Wed, 30 Dec 2020 15:49:27 -0600 Subject: [PATCH 20/36] Removing test during verify:web --- push-notifications/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/push-notifications/package.json b/push-notifications/package.json index f5b5ba3c4..819514f6b 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -30,7 +30,7 @@ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", "verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin && cd ..", "verify:android": "cd android && ./gradlew clean build test && cd ..", - "verify:web": "npm run build && npm test", + "verify:web": "npm run build", "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint", "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- autocorrect --format", "eslint": "eslint . --ext ts", From eb4c6fa3fcee3b0c1253353bff93d74735c7b771 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Wed, 30 Dec 2020 14:27:49 -0800 Subject: [PATCH 21/36] wip: documentation updates --- push-notifications/README.md | 78 ++++++++++++++++++++++++++- push-notifications/src/definitions.ts | 41 +++++++++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index a72e6c3d8..f037dfe99 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -9,6 +9,55 @@ npm install @capacitor/push-notifications npx cap sync ``` +## iOS + +TODO: On iOS you must enable the Push Notifications capability. See [Setting Capabilities](https://capacitorjs.com/docs/v3/ios/configuration#setting-capabilities) for + +TODO: mention `GoogleServices-Info.plist` + +TODO: mention AppDelegate.swift + +## Android + +TODO: mention `google-services.json` + +--- + +OLD STUFF + +## Push Notifications icon + +On Android, the Push Notifications icon with the appropriate name should be added to the `AndroidManifest.xml` file: + +```xml + +``` + +If no icon is specified Android will use the application icon, but push icon should be white pixels on a transparent backdrop. As the application icon is not usually like that, it will show a white square or circle. So it's recommended to provide the separate icon for Push Notifications. + +Android Studio has an icon generator you can use to create your Push Notifications icon. + +## Push notifications appearance in foreground + +On iOS you can configure the way the push notifications are displayed when the app is in foreground by providing the `presentationOptions` in your `capacitor.config.json` as an Array of Strings you can combine. + +Possible values are: +* `badge`: badge count on the app icon is updated (default value) +* `sound`: the device will ring/vibrate when the push notification is received +* `alert`: the push notification is displayed in a native dialog + +An empty Array can be provided if none of the previous options are desired. `pushNotificationReceived` event will still be fired with the push notification information. + +```json +"plugins": { + "PushNotifications": { + "presentationOptions": ["badge", "sound", "alert"] + } +} +``` + +--- + ## API @@ -41,10 +90,13 @@ register() => Promise ``` Register the app to receive push notifications. -Will trigger registration event with the push token -or registrationError if there was some problem. + +Will trigger registration event with the push token or registrationError if there was some problem. + Doesn't prompt the user for notification permissions, use requestPermission() first. +**Since:** 1.0.0 + -------------------- @@ -58,6 +110,8 @@ Returns the notifications that are visible on the notifications screen. **Returns:** Promise<PushNotificationDeliveredList> +**Since:** 1.0.0 + -------------------- @@ -73,6 +127,8 @@ Removes the specified notifications from the notifications screen. | --------------- | --------------------------------------------------------------------------------------- | -------------------------------- | | **`delivered`** | PushNotificationDeliveredList | list of delivered notifications. | +**Since:** 1.0.0 + -------------------- @@ -84,6 +140,8 @@ removeAllDeliveredNotifications() => Promise Removes all the notifications from the notifications screen. +**Since:** 1.0.0 + -------------------- @@ -99,6 +157,8 @@ On Android O or newer (SDK 26+) creates a notification channel. | ------------- | ------------------------------------------- | ----------- | | **`channel`** | Channel | to create. | +**Since:** 1.0.0 + -------------------- @@ -114,6 +174,8 @@ On Android O or newer (SDK 26+) deletes a notification channel. | ------------- | ------------------------------------------- | ----------- | | **`channel`** | Channel | to delete. | +**Since:** 1.0.0 + -------------------- @@ -127,6 +189,8 @@ On Android O or newer (SDK 26+) list the available notification channels. **Returns:** Promise<ListChannelsResult> +**Since:** 1.0.0 + -------------------- @@ -176,6 +240,8 @@ Provides the push notification token. **Returns:** PluginListenerHandle +**Since:** 1.0.0 + -------------------- @@ -195,6 +261,8 @@ Provides an error with the registration problem. **Returns:** PluginListenerHandle +**Since:** 1.0.0 + -------------------- @@ -213,6 +281,8 @@ Event called when the device receives a push notification. **Returns:** PluginListenerHandle +**Since:** 1.0.0 + -------------------- @@ -231,6 +301,8 @@ Event called when an action is performed on a pusn notification. **Returns:** PluginListenerHandle +**Since:** 1.0.0 + -------------------- @@ -242,6 +314,8 @@ removeAllListeners() => void Remove all native listeners for this plugin. +**Since:** 1.0.0 + -------------------- diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index f5d7176c5..11520a67d 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -3,38 +3,59 @@ import type { PermissionState, PluginListenerHandle } from '@capacitor/core'; export interface PushNotificationsPlugin { /** * Register the app to receive push notifications. - * Will trigger registration event with the push token - * or registrationError if there was some problem. + * + * Will trigger registration event with the push token or registrationError if there was some problem. + * * Doesn't prompt the user for notification permissions, use requestPermission() first. + * + * @since 1.0.0 */ register(): Promise; + /** * Returns the notifications that are visible on the notifications screen. + * + * @since 1.0.0 */ getDeliveredNotifications(): Promise; + /** * Removes the specified notifications from the notifications screen. + * * @param delivered list of delivered notifications. + * @since 1.0.0 */ removeDeliveredNotifications( delivered: PushNotificationDeliveredList, ): Promise; + /** * Removes all the notifications from the notifications screen. + * + * @since 1.0.0 */ removeAllDeliveredNotifications(): Promise; + /** * On Android O or newer (SDK 26+) creates a notification channel. + * * @param channel to create. + * @since 1.0.0 */ createChannel(channel: Channel): Promise; + /** * On Android O or newer (SDK 26+) deletes a notification channel. + * * @param channel to delete. + * @since 1.0.0 */ deleteChannel(channel: Channel): Promise; + /** * On Android O or newer (SDK 26+) list the available notification channels. + * + * @since 1.0.0 */ listChannels(): Promise; @@ -55,43 +76,57 @@ export interface PushNotificationsPlugin { /** * Event called when the push notification registration finished without problems. * Provides the push notification token. + * * @param eventName registration. * @param listenerFunc callback with the push token. + * @since 1.0.0 */ addListener( eventName: 'registration', listenerFunc: (token: PushNotificationToken) => void, ): PluginListenerHandle; + /** * Event called when the push notification registration finished with problems. * Provides an error with the registration problem. + * * @param eventName registrationError. * @param listenerFunc callback with the registration error. + * @since 1.0.0 */ addListener( eventName: 'registrationError', listenerFunc: (error: any) => void, ): PluginListenerHandle; + /** * Event called when the device receives a push notification. + * * @param eventName pushNotificationReceived. * @param listenerFunc callback with the received notification. + * @since 1.0.0 */ addListener( eventName: 'pushNotificationReceived', listenerFunc: (notification: PushNotificationSchema) => void, ): PluginListenerHandle; + /** * Event called when an action is performed on a pusn notification. + * * @param eventName pushNotificationActionPerformed. * @param listenerFunc callback with the notification action. + * @since 1.0.0 */ addListener( eventName: 'pushNotificationActionPerformed', listenerFunc: (notification: PushNotificationActionPerformed) => void, ): PluginListenerHandle; + /** * Remove all native listeners for this plugin. + * + * @since 1.0.0 */ removeAllListeners(): void; } @@ -106,11 +141,13 @@ export interface PushNotificationSchema { data: any; click_action?: string; link?: string; + /** * Android only: set the group identifier for notification grouping, like * threadIdentifier on iOS. */ group?: string; + /** * Android only: designate this notification as the summary for a group * (should be used with the `group` property). From f471ad412f60a1b75fb6c452a4f7572fdf87cf6e Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Wed, 30 Dec 2020 14:29:54 -0800 Subject: [PATCH 22/36] add since to props --- push-notifications/src/definitions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index 11520a67d..f67c95788 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -145,12 +145,16 @@ export interface PushNotificationSchema { /** * Android only: set the group identifier for notification grouping, like * threadIdentifier on iOS. + * + * @since 1.0.0 */ group?: string; /** * Android only: designate this notification as the summary for a group * (should be used with the `group` property). + * + * @since 1.0.0 */ groupSummary?: boolean; } From 0f4488751dd0c7cdb24790013778d9617eb7d95a Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Tue, 5 Jan 2021 19:17:41 -0600 Subject: [PATCH 23/36] Added more package documentation --- push-notifications/README.md | 102 +++++++++++++----------- push-notifications/src/definitions.ts | 109 ++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 47 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index f037dfe99..fd2c63a2a 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -11,20 +11,28 @@ npx cap sync ## iOS -TODO: On iOS you must enable the Push Notifications capability. See [Setting Capabilities](https://capacitorjs.com/docs/v3/ios/configuration#setting-capabilities) for +On iOS you must enable the Push Notifications capability. See [Setting Capabilities](https://capacitorjs.com/docs/v3/ios/configuration#setting-capabilities) for instructions on how to enable the capability. -TODO: mention `GoogleServices-Info.plist` +The Push Notification API uses [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) SDK for handling notifications. See [Set up a Firebase Cloud Messaging client app on iOS](https://firebase.google.com/docs/cloud-messaging/ios/client) and follow the instructions for creating a Firebase project and registering your application. Do not add the Firebase SDK to your app - the Push Notifications provides that for you. All that is required is your Firebase project `GoogleService-Info.plist` file added to your Xcode project. -TODO: mention AppDelegate.swift +After setting up your Firebase project, add the following to your application AppDelegate.swift + +```swift +func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken) +} + +func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) +} +``` ## Android -TODO: mention `google-services.json` +The Push Notification API uses [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) SDK for handling notifications. See [Set up a Firebase Cloud Messaging client app on Android](https://firebase.google.com/docs/cloud-messaging/android/client) and follow the instructions for creating a Firebase project and registering your application. There is no need to add the Firebase SDK to your app or edit your app manifest - the Push Notifications provides that for you. All that is required is your Firebase project's `google-services.json` file added to the module (app-level) directory of your app. --- -OLD STUFF - ## Push Notifications icon On Android, the Push Notifications icon with the appropriate name should be added to the `AndroidManifest.xml` file: @@ -324,55 +332,55 @@ Remove all native listeners for this plugin. #### PushNotificationDeliveredList -| Prop | Type | -| ------------------- | ------------------------------------- | -| **`notifications`** | PushNotificationSchema[] | +| Prop | Type | Since | +| ------------------- | ------------------------------------- | ----- | +| **`notifications`** | PushNotificationSchema[] | 1.0.0 | #### PushNotificationSchema -| Prop | Type | Description | -| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------- | -| **`title`** | string | | -| **`subtitle`** | string | | -| **`body`** | string | | -| **`id`** | string | | -| **`badge`** | number | | -| **`notification`** | any | | -| **`data`** | any | | -| **`click_action`** | string | | -| **`link`** | string | | -| **`group`** | string | Android only: set the group identifier for notification grouping, like threadIdentifier on iOS. | -| **`groupSummary`** | boolean | Android only: designate this notification as the summary for a group (should be used with the `group` property). | +| Prop | Type | Description | Since | +| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------- | ----- | +| **`title`** | string | The notification title | 1.0.0 | +| **`subtitle`** | string | The notification sub title | 1.0.0 | +| **`body`** | string | The main text payload for the notification | 1.0.0 | +| **`id`** | string | The notification identifier | 1.0.0 | +| **`badge`** | number | The number to display for the app icon badge | 1.0.0 | +| **`notification`** | any | | 1.0.0 | +| **`data`** | any | The notification identifier | 1.0.0 | +| **`click_action`** | string | | 1.0.0 | +| **`link`** | string | | 1.0.0 | +| **`group`** | string | Android only: set the group identifier for notification grouping, like threadIdentifier on iOS. | 1.0.0 | +| **`groupSummary`** | boolean | Android only: designate this notification as the summary for a group (should be used with the `group` property). | 1.0.0 | #### Channel -| Prop | Type | -| ----------------- | ---------------------------------- | -| **`id`** | string | -| **`name`** | string | -| **`description`** | string | -| **`sound`** | string | -| **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | -| **`visibility`** | 0 \| 1 \| -1 | -| **`lights`** | boolean | -| **`lightColor`** | string | -| **`vibration`** | boolean | +| Prop | Type | Description | Since | +| ----------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| **`id`** | string | Android only: The channel identifier | 1.0.0 | +| **`name`** | string | Android only: Sets the user visible name of this channel. | 1.0.0 | +| **`description`** | string | Android only: Sets the user visible description of this channel. | 1.0.0 | +| **`sound`** | string | Android only: Sets the sound that should be played for notifications posted to this channel. Notification channels with an importance of at least 3 should have a sound. Should specifify the file name of a sound file relative to the android app res/raw directory. | 1.0.0 | +| **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | Android only: Sets the level of interruption of this notification channel. | 1.0.0 | +| **`visibility`** | 0 \| 1 \| -1 | Android only: Sets whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.0.0 | +| **`lights`** | boolean | Android only: Sets whether notifications posted to this channel should display notification lights, on devices that support that feature. | 1.0.0 | +| **`lightColor`** | string | Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. Supported color formats: #RRGGBB #AARRGGBB The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal | 1.0.0 | +| **`vibration`** | boolean | Android only: Sets whether notification posted to this channel should vibrate. | 1.0.0 | #### ListChannelsResult -| Prop | Type | -| -------------- | ---------------------- | -| **`channels`** | Channel[] | +| Prop | Type | Since | +| -------------- | ---------------------- | ----- | +| **`channels`** | Channel[] | 1.0.0 | #### PermissionStatus -| Prop | Type | -| ------------- | ------------------------------------------------------------------------- | -| **`receive`** | "prompt" \| "prompt-with-rationale" \| "granted" \| "denied" | +| Prop | Type | Since | +| ------------- | ------------------------------------------------------------------------- | ----- | +| **`receive`** | "prompt" \| "prompt-with-rationale" \| "granted" \| "denied" | 1.0.0 | #### PluginListenerHandle @@ -384,17 +392,17 @@ Remove all native listeners for this plugin. #### PushNotificationToken -| Prop | Type | -| ----------- | ------------------- | -| **`value`** | string | +| Prop | Type | Since | +| ----------- | ------------------- | ----- | +| **`value`** | string | 1.0.0 | #### PushNotificationActionPerformed -| Prop | Type | -| ------------------ | ------------------------------------------------------------------------- | -| **`actionId`** | string | -| **`inputValue`** | string | -| **`notification`** | PushNotificationSchema | +| Prop | Type | Since | +| ------------------ | ------------------------------------------------------------------------- | ----- | +| **`actionId`** | string | 1.0.0 | +| **`inputValue`** | string | 1.0.0 | +| **`notification`** | PushNotificationSchema | 1.0.0 | diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index f67c95788..79d638eb9 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -132,14 +132,47 @@ export interface PushNotificationsPlugin { } export interface PushNotificationSchema { + /** + * The notification title + * @since 1.0.0 + */ title?: string; + /** + * The notification sub title + * @since 1.0.0 + */ subtitle?: string; + /** + * The main text payload for the notification + * @since 1.0.0 + */ body?: string; + /** + * The notification identifier + * @since 1.0.0 + */ id: string; + /** + * The number to display for the app icon badge + * @since 1.0.0 + */ badge?: number; + /** + * @since 1.0.0 + */ notification?: any; + /** + * The notification identifier + * @since 1.0.0 + */ data: any; + /** + * @since 1.0.0 + */ click_action?: string; + /** + * @since 1.0.0 + */ link?: string; /** @@ -160,35 +193,111 @@ export interface PushNotificationSchema { } export interface PushNotificationActionPerformed { + /** + * @since 1.0.0 + */ actionId: string; + /** + * @since 1.0.0 + */ inputValue?: string; + /** + * @since 1.0.0 + */ notification: PushNotificationSchema; } export interface PushNotificationToken { + /** + * @since 1.0.0 + */ value: string; } export interface PushNotificationDeliveredList { + /** + * @since 1.0.0 + */ notifications: PushNotificationSchema[]; } export interface Channel { + /** + * Android only: The channel identifier + * @since 1.0.0 + */ id: string; + /** + * Android only: Sets the user visible name of this channel. + * + * @since 1.0.0 + */ name: string; + /** + * Android only: Sets the user visible description of this channel. + * + * @since 1.0.0 + */ description?: string; + /** + * Android only: Sets the sound that should be played for notifications posted to this channel. + * Notification channels with an importance of at least 3 should have a sound. + * + * Should specifify the file name of a sound file relative to the android app res/raw directory. + * @since 1.0.0 + * @example "jingle.wav" + */ sound?: string; + /** + * Android only: Sets the level of interruption of this notification channel. + * + * @since 1.0.0 + */ importance: 1 | 2 | 3 | 4 | 5; + /** + * Android only: Sets whether notifications posted to this channel appear on the + * lockscreen or not, and if so, whether they appear in a redacted form. + * + * @since 1.0.0 + */ visibility?: -1 | 0 | 1; + /** + * Android only: Sets whether notifications posted to this channel should display notification lights, on devices that support that feature. + * + * @since 1.0.0 + */ lights?: boolean; + /** + * Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. + * + * Supported color formats: + * + * #RRGGBB + * #AARRGGBB + * + * The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal + * + * @since 1.0.0 + */ lightColor?: string; + /** + * Android only: Sets whether notification posted to this channel should vibrate. + * + * @since 1.0.0 + */ vibration?: boolean; } export interface ListChannelsResult { + /** + * @since 1.0.0 + */ channels: Channel[]; } export interface PermissionStatus { + /** + * @since 1.0.0 + */ receive: PermissionState; } From 5a43080b1f4721d4d6a00faa2b42c92920f1cff7 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Thu, 7 Jan 2021 10:29:16 -0800 Subject: [PATCH 24/36] add plugin to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a75ca4962..e2853ce18 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ This repository contains the official Capacitor plugins maintained by the Capaci | [`@capacitor/keyboard`](https://capacitorjs.com/docs/v3/apis/keyboard) | [`./keyboard`](./keyboard) | [![npm badge](https://img.shields.io/npm/v/@capacitor/keyboard?style=flat-square)](https://www.npmjs.com/package/@capacitor/keyboard) | [`@capacitor/motion`](https://capacitorjs.com/docs/v3/apis/motion) | [`./motion`](./motion) | [![npm badge](https://img.shields.io/npm/v/@capacitor/motion?style=flat-square)](https://www.npmjs.com/package/@capacitor/motion) | [`@capacitor/network`](https://capacitorjs.com/docs/v3/apis/network) | [`./network`](./network) | [![npm badge](https://img.shields.io/npm/v/@capacitor/network?style=flat-square)](https://www.npmjs.com/package/@capacitor/network) +| [`@capacitor/push-notifications`](https://capacitorjs.com/docs/v3/apis/push-notifications) | [`./push-notifications`](./push-notifications) | [![npm badge](https://img.shields.io/npm/v/@capacitor/push-notifications?style=flat-square)](https://www.npmjs.com/package/@capacitor/push-notifications) | [`@capacitor/screen-reader`](https://capacitorjs.com/docs/v3/apis/screen-reader) | [`./screen-reader`](./screen-reader) | [![npm badge](https://img.shields.io/npm/v/@capacitor/screen-reader?style=flat-square)](https://www.npmjs.com/package/@capacitor/screen-reader) | [`@capacitor/share`](https://capacitorjs.com/docs/v3/apis/share) | [`./share`](./share) | [![npm badge](https://img.shields.io/npm/v/@capacitor/share?style=flat-square)](https://www.npmjs.com/package/@capacitor/share) | [`@capacitor/splash-screen`](https://capacitorjs.com/docs/v3/apis/splash-screen) | [`./splash-screen`](./splash-screen) | [![npm badge](https://img.shields.io/npm/v/@capacitor/splash-screen?style=flat-square)](https://www.npmjs.com/package/@capacitor/splash-screen) From b5826f9c1dfc754dff83de6e5c3de47f1b6a5539 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Thu, 7 Jan 2021 19:44:17 -0800 Subject: [PATCH 25/36] words --- push-notifications/README.md | 132 ++++++++++++++------------ push-notifications/src/definitions.ts | 129 +++++++++++++++---------- 2 files changed, 148 insertions(+), 113 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index fd2c63a2a..888a93017 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -99,9 +99,9 @@ register() => Promise Register the app to receive push notifications. -Will trigger registration event with the push token or registrationError if there was some problem. - -Doesn't prompt the user for notification permissions, use requestPermission() first. +This method will trigger the `'registration'` event with the push token or +`'registrationError'` if there was a problem. It does prompt the user for +notification permissions, use `requestPermissions()` first. **Since:** 1.0.0 @@ -114,7 +114,7 @@ Doesn't prompt the user for notification permissions, use requestPermission() fi getDeliveredNotifications() => Promise ``` -Returns the notifications that are visible on the notifications screen. +Get a list of notifications that are visible on the notifications screen. **Returns:** Promise<PushNotificationDeliveredList> @@ -129,11 +129,11 @@ Returns the notifications that are visible on the notifications screen. removeDeliveredNotifications(delivered: PushNotificationDeliveredList) => Promise ``` -Removes the specified notifications from the notifications screen. +Remove the specified notifications from the notifications screen. -| Param | Type | Description | -| --------------- | --------------------------------------------------------------------------------------- | -------------------------------- | -| **`delivered`** | PushNotificationDeliveredList | list of delivered notifications. | +| Param | Type | +| --------------- | --------------------------------------------------------------------------------------- | +| **`delivered`** | PushNotificationDeliveredList | **Since:** 1.0.0 @@ -146,7 +146,7 @@ Removes the specified notifications from the notifications screen. removeAllDeliveredNotifications() => Promise ``` -Removes all the notifications from the notifications screen. +Remove all the notifications from the notifications screen. **Since:** 1.0.0 @@ -159,11 +159,13 @@ Removes all the notifications from the notifications screen. createChannel(channel: Channel) => Promise ``` -On Android O or newer (SDK 26+) creates a notification channel. +Create a notification channel. + +Only available on Android O or newer (SDK 26+). -| Param | Type | Description | -| ------------- | ------------------------------------------- | ----------- | -| **`channel`** | Channel | to create. | +| Param | Type | +| ------------- | ------------------------------------------- | +| **`channel`** | Channel | **Since:** 1.0.0 @@ -176,11 +178,13 @@ On Android O or newer (SDK 26+) creates a notification channel. deleteChannel(channel: Channel) => Promise ``` -On Android O or newer (SDK 26+) deletes a notification channel. +Delete a notification channel. + +Only available on Android O or newer (SDK 26+). -| Param | Type | Description | -| ------------- | ------------------------------------------- | ----------- | -| **`channel`** | Channel | to delete. | +| Param | Type | +| ------------- | ------------------------------------------- | +| **`channel`** | Channel | **Since:** 1.0.0 @@ -193,7 +197,9 @@ On Android O or newer (SDK 26+) deletes a notification channel. listChannels() => Promise ``` -On Android O or newer (SDK 26+) list the available notification channels. +List the available notification channels. + +Only available on Android O or newer (SDK 26+). **Returns:** Promise<ListChannelsResult> @@ -238,13 +244,14 @@ Request permission to receive push notifications. addListener(eventName: 'registration', listenerFunc: (token: PushNotificationToken) => void) => PluginListenerHandle ``` -Event called when the push notification registration finished without problems. +Called when the push notification registration finishes without problems. + Provides the push notification token. -| Param | Type | Description | -| ------------------ | ------------------------------------------------------------------------------------------- | ----------------------------- | -| **`eventName`** | "registration" | registration. | -| **`listenerFunc`** | (token: PushNotificationToken) => void | callback with the push token. | +| Param | Type | +| ------------------ | ------------------------------------------------------------------------------------------- | +| **`eventName`** | "registration" | +| **`listenerFunc`** | (token: PushNotificationToken) => void | **Returns:** PluginListenerHandle @@ -259,13 +266,14 @@ Provides the push notification token. addListener(eventName: 'registrationError', listenerFunc: (error: any) => void) => PluginListenerHandle ``` -Event called when the push notification registration finished with problems. +Called when the push notification registration finished with problems. + Provides an error with the registration problem. -| Param | Type | Description | -| ------------------ | ------------------------------------ | ------------------------------------- | -| **`eventName`** | "registrationError" | registrationError. | -| **`listenerFunc`** | (error: any) => void | callback with the registration error. | +| Param | Type | +| ------------------ | ------------------------------------ | +| **`eventName`** | "registrationError" | +| **`listenerFunc`** | (error: any) => void | **Returns:** PluginListenerHandle @@ -280,12 +288,12 @@ Provides an error with the registration problem. addListener(eventName: 'pushNotificationReceived', listenerFunc: (notification: PushNotificationSchema) => void) => PluginListenerHandle ``` -Event called when the device receives a push notification. +Called when the device receives a push notification. -| Param | Type | Description | -| ------------------ | ---------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| **`eventName`** | "pushNotificationReceived" | pushNotificationReceived. | -| **`listenerFunc`** | (notification: PushNotificationSchema) => void | callback with the received notification. | +| Param | Type | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| **`eventName`** | "pushNotificationReceived" | +| **`listenerFunc`** | (notification: PushNotificationSchema) => void | **Returns:** PluginListenerHandle @@ -300,12 +308,12 @@ Event called when the device receives a push notification. addListener(eventName: 'pushNotificationActionPerformed', listenerFunc: (notification: PushNotificationActionPerformed) => void) => PluginListenerHandle ``` -Event called when an action is performed on a pusn notification. +Called when an action is performed on a push notification. -| Param | Type | Description | -| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| **`eventName`** | "pushNotificationActionPerformed" | pushNotificationActionPerformed. | -| **`listenerFunc`** | (notification: PushNotificationActionPerformed) => void | callback with the notification action. | +| Param | Type | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| **`eventName`** | "pushNotificationActionPerformed" | +| **`listenerFunc`** | (notification: PushNotificationActionPerformed) => void | **Returns:** PluginListenerHandle @@ -339,34 +347,34 @@ Remove all native listeners for this plugin. #### PushNotificationSchema -| Prop | Type | Description | Since | -| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------- | ----- | -| **`title`** | string | The notification title | 1.0.0 | -| **`subtitle`** | string | The notification sub title | 1.0.0 | -| **`body`** | string | The main text payload for the notification | 1.0.0 | -| **`id`** | string | The notification identifier | 1.0.0 | -| **`badge`** | number | The number to display for the app icon badge | 1.0.0 | -| **`notification`** | any | | 1.0.0 | -| **`data`** | any | The notification identifier | 1.0.0 | -| **`click_action`** | string | | 1.0.0 | -| **`link`** | string | | 1.0.0 | -| **`group`** | string | Android only: set the group identifier for notification grouping, like threadIdentifier on iOS. | 1.0.0 | -| **`groupSummary`** | boolean | Android only: designate this notification as the summary for a group (should be used with the `group` property). | 1.0.0 | +| Prop | Type | Description | Since | +| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------- | ----- | +| **`title`** | string | The notification title. | 1.0.0 | +| **`subtitle`** | string | The notification subtitle. | 1.0.0 | +| **`body`** | string | The main text payload for the notification. | 1.0.0 | +| **`id`** | string | The notification identifier. | 1.0.0 | +| **`badge`** | number | The number to display for the app icon badge. | 1.0.0 | +| **`notification`** | any | | 1.0.0 | +| **`data`** | any | | 1.0.0 | +| **`click_action`** | string | | 1.0.0 | +| **`link`** | string | | 1.0.0 | +| **`group`** | string | Set the group identifier for notification grouping Only available on Android. Works like `threadIdentifier` on iOS. | 1.0.0 | +| **`groupSummary`** | boolean | Designate this notification as the summary for an associated `group`. Only available on Android. | 1.0.0 | #### Channel -| Prop | Type | Description | Since | -| ----------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`id`** | string | Android only: The channel identifier | 1.0.0 | -| **`name`** | string | Android only: Sets the user visible name of this channel. | 1.0.0 | -| **`description`** | string | Android only: Sets the user visible description of this channel. | 1.0.0 | -| **`sound`** | string | Android only: Sets the sound that should be played for notifications posted to this channel. Notification channels with an importance of at least 3 should have a sound. Should specifify the file name of a sound file relative to the android app res/raw directory. | 1.0.0 | -| **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | Android only: Sets the level of interruption of this notification channel. | 1.0.0 | -| **`visibility`** | 0 \| 1 \| -1 | Android only: Sets whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.0.0 | -| **`lights`** | boolean | Android only: Sets whether notifications posted to this channel should display notification lights, on devices that support that feature. | 1.0.0 | -| **`lightColor`** | string | Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. Supported color formats: #RRGGBB #AARRGGBB The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal | 1.0.0 | -| **`vibration`** | boolean | Android only: Sets whether notification posted to this channel should vibrate. | 1.0.0 | +| Prop | Type | Description | Since | +| ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| **`id`** | string | The channel identifier. | 1.0.0 | +| **`name`** | string | The human-friendly name of this channel (presented to the user). | 1.0.0 | +| **`description`** | string | The description of this channel (presented to the user). | 1.0.0 | +| **`sound`** | string | The sound that should be played for notifications posted to this channel. Notification channels with an importance of at least `3` should have a sound. The file name of a sound file should be specified relative to the android app `res/raw` directory. | 1.0.0 | +| **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | The level of interruption for notifications posted to this channel. | 1.0.0 | +| **`visibility`** | 0 \| 1 \| -1 | The visibility of notifications posted to this channel. This setting is for whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.0.0 | +| **`lights`** | boolean | Whether notifications posted to this channel should display notification lights, on devices that support it. | 1.0.0 | +| **`lightColor`** | string | The light color for notifications posted to this channel. Only supported if lights are enabled on this channel and the device supports it. Supported color formats are `#RRGGBB` and `#AARRGGBB`. | 1.0.0 | +| **`vibration`** | boolean | Whether notifications posted to this channel should vibrate. | 1.0.0 | #### ListChannelsResult diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index 79d638eb9..687f71a87 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -4,25 +4,24 @@ export interface PushNotificationsPlugin { /** * Register the app to receive push notifications. * - * Will trigger registration event with the push token or registrationError if there was some problem. - * - * Doesn't prompt the user for notification permissions, use requestPermission() first. + * This method will trigger the `'registration'` event with the push token or + * `'registrationError'` if there was a problem. It does prompt the user for + * notification permissions, use `requestPermissions()` first. * * @since 1.0.0 */ register(): Promise; /** - * Returns the notifications that are visible on the notifications screen. + * Get a list of notifications that are visible on the notifications screen. * * @since 1.0.0 */ getDeliveredNotifications(): Promise; /** - * Removes the specified notifications from the notifications screen. + * Remove the specified notifications from the notifications screen. * - * @param delivered list of delivered notifications. * @since 1.0.0 */ removeDeliveredNotifications( @@ -30,30 +29,34 @@ export interface PushNotificationsPlugin { ): Promise; /** - * Removes all the notifications from the notifications screen. + * Remove all the notifications from the notifications screen. * * @since 1.0.0 */ removeAllDeliveredNotifications(): Promise; /** - * On Android O or newer (SDK 26+) creates a notification channel. + * Create a notification channel. + * + * Only available on Android O or newer (SDK 26+). * - * @param channel to create. * @since 1.0.0 */ createChannel(channel: Channel): Promise; /** - * On Android O or newer (SDK 26+) deletes a notification channel. + * Delete a notification channel. + * + * Only available on Android O or newer (SDK 26+). * - * @param channel to delete. * @since 1.0.0 */ deleteChannel(channel: Channel): Promise; /** - * On Android O or newer (SDK 26+) list the available notification channels. + * List the available notification channels. + * + * Only available on Android O or newer (SDK 26+). * * @since 1.0.0 */ @@ -74,11 +77,10 @@ export interface PushNotificationsPlugin { requestPermissions(): Promise; /** - * Event called when the push notification registration finished without problems. + * Called when the push notification registration finishes without problems. + * * Provides the push notification token. * - * @param eventName registration. - * @param listenerFunc callback with the push token. * @since 1.0.0 */ addListener( @@ -87,11 +89,10 @@ export interface PushNotificationsPlugin { ): PluginListenerHandle; /** - * Event called when the push notification registration finished with problems. + * Called when the push notification registration finished with problems. + * * Provides an error with the registration problem. * - * @param eventName registrationError. - * @param listenerFunc callback with the registration error. * @since 1.0.0 */ addListener( @@ -100,10 +101,8 @@ export interface PushNotificationsPlugin { ): PluginListenerHandle; /** - * Event called when the device receives a push notification. + * Called when the device receives a push notification. * - * @param eventName pushNotificationReceived. - * @param listenerFunc callback with the received notification. * @since 1.0.0 */ addListener( @@ -112,10 +111,8 @@ export interface PushNotificationsPlugin { ): PluginListenerHandle; /** - * Event called when an action is performed on a pusn notification. + * Called when an action is performed on a push notification. * - * @param eventName pushNotificationActionPerformed. - * @param listenerFunc callback with the notification action. * @since 1.0.0 */ addListener( @@ -133,59 +130,73 @@ export interface PushNotificationsPlugin { export interface PushNotificationSchema { /** - * The notification title + * The notification title. + * * @since 1.0.0 */ title?: string; + /** - * The notification sub title + * The notification subtitle. + * * @since 1.0.0 */ subtitle?: string; + /** - * The main text payload for the notification + * The main text payload for the notification. + * * @since 1.0.0 */ body?: string; + /** - * The notification identifier + * The notification identifier. + * * @since 1.0.0 */ id: string; + /** - * The number to display for the app icon badge + * The number to display for the app icon badge. + * * @since 1.0.0 */ badge?: number; + /** * @since 1.0.0 */ notification?: any; + /** - * The notification identifier * @since 1.0.0 */ data: any; + /** * @since 1.0.0 */ click_action?: string; + /** * @since 1.0.0 */ link?: string; /** - * Android only: set the group identifier for notification grouping, like - * threadIdentifier on iOS. + * Set the group identifier for notification grouping + * + * Only available on Android. Works like `threadIdentifier` on iOS. * * @since 1.0.0 */ group?: string; /** - * Android only: designate this notification as the summary for a group - * (should be used with the `group` property). + * Designate this notification as the summary for an associated `group`. + * + * Only available on Android. * * @since 1.0.0 */ @@ -197,10 +208,12 @@ export interface PushNotificationActionPerformed { * @since 1.0.0 */ actionId: string; + /** * @since 1.0.0 */ inputValue?: string; + /** * @since 1.0.0 */ @@ -223,65 +236,79 @@ export interface PushNotificationDeliveredList { export interface Channel { /** - * Android only: The channel identifier + * The channel identifier. + * * @since 1.0.0 */ id: string; + /** - * Android only: Sets the user visible name of this channel. + * The human-friendly name of this channel (presented to the user). * * @since 1.0.0 */ name: string; + /** - * Android only: Sets the user visible description of this channel. + * The description of this channel (presented to the user). * * @since 1.0.0 */ description?: string; + /** - * Android only: Sets the sound that should be played for notifications posted to this channel. - * Notification channels with an importance of at least 3 should have a sound. + * The sound that should be played for notifications posted to this channel. + * + * Notification channels with an importance of at least `3` should have a + * sound. + * + * The file name of a sound file should be specified relative to the android + * app `res/raw` directory. * - * Should specifify the file name of a sound file relative to the android app res/raw directory. * @since 1.0.0 * @example "jingle.wav" */ sound?: string; + /** - * Android only: Sets the level of interruption of this notification channel. + * The level of interruption for notifications posted to this channel. * * @since 1.0.0 */ importance: 1 | 2 | 3 | 4 | 5; + /** - * Android only: Sets whether notifications posted to this channel appear on the - * lockscreen or not, and if so, whether they appear in a redacted form. + * The visibility of notifications posted to this channel. + * + * This setting is for whether notifications posted to this channel appear on + * the lockscreen or not, and if so, whether they appear in a redacted form. * * @since 1.0.0 */ visibility?: -1 | 0 | 1; + /** - * Android only: Sets whether notifications posted to this channel should display notification lights, on devices that support that feature. + * Whether notifications posted to this channel should display notification + * lights, on devices that support it. * * @since 1.0.0 */ lights?: boolean; + /** - * Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. + * The light color for notifications posted to this channel. * - * Supported color formats: + * Only supported if lights are enabled on this channel and the device + * supports it. * - * #RRGGBB - * #AARRGGBB - * - * The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal + * Supported color formats are `#RRGGBB` and `#AARRGGBB`. * * @since 1.0.0 */ lightColor?: string; + /** - * Android only: Sets whether notification posted to this channel should vibrate. + * Whether notifications posted to this channel should vibrate. * * @since 1.0.0 */ From b8a67e9ab57d87d5b19ed83e86e97d17e910c97b Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 13:54:23 -0600 Subject: [PATCH 26/36] Migrate from Color.parseColor() to WebColor.parseColor() --- push-notifications/README.md | 2 +- .../pushnotifications/NotificationChannelManager.java | 4 +++- push-notifications/src/definitions.ts | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index fd2c63a2a..ca789e93c 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -365,7 +365,7 @@ Remove all native listeners for this plugin. | **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | Android only: Sets the level of interruption of this notification channel. | 1.0.0 | | **`visibility`** | 0 \| 1 \| -1 | Android only: Sets whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.0.0 | | **`lights`** | boolean | Android only: Sets whether notifications posted to this channel should display notification lights, on devices that support that feature. | 1.0.0 | -| **`lightColor`** | string | Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. Supported color formats: #RRGGBB #AARRGGBB The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal | 1.0.0 | +| **`lightColor`** | string | Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. Supported color formats: #RRGGBB #RRGGBBAA The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal | 1.0.0 | | **`vibration`** | boolean | Android only: Sets whether notification posted to this channel should vibrate. | 1.0.0 | diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java index 0551094eb..be6b9f046 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java @@ -12,6 +12,8 @@ import com.getcapacitor.JSObject; import com.getcapacitor.Logger; import com.getcapacitor.PluginCall; +import com.getcapacitor.util.WebColor; + import java.util.List; public class NotificationChannelManager { @@ -67,7 +69,7 @@ public void createChannel(JSObject channel) { String lightColor = channel.getString(CHANNEL_LIGHT_COLOR); if (lightColor != null) { try { - notificationChannel.setLightColor(Color.parseColor(lightColor)); + notificationChannel.setLightColor(WebColor.parseColor(lightColor)); } catch (IllegalArgumentException ex) { Logger.error(Logger.tags("NotificationChannel"), "Invalid color provided for light color.", null); } diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index 79d638eb9..0a6dcfb2a 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -270,10 +270,9 @@ export interface Channel { /** * Android only: Sets the notification light color for notifications posted to this channel, if lights are enabled on this channel and the device supports that feature. * - * Supported color formats: - * - * #RRGGBB - * #AARRGGBB + * Supported color formats: + * #RRGGBB + * #RRGGBBAA * * The following names are also accepted: red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal * From 3be7d301a7b5da0ffd9f875717002c9e6d5ff794 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 14:00:32 -0600 Subject: [PATCH 27/36] updating lightColor documentation --- push-notifications/src/definitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index 687f71a87..5ced24c7d 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -301,7 +301,7 @@ export interface Channel { * Only supported if lights are enabled on this channel and the device * supports it. * - * Supported color formats are `#RRGGBB` and `#AARRGGBB`. + * Supported color formats are `#RRGGBB` and `#RRGGBBAA`. * * @since 1.0.0 */ From dad2f4d185f5d3eff3ea2182dfb3c872c3738e98 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 14:06:34 -0600 Subject: [PATCH 28/36] lint --- .../plugins/pushnotifications/NotificationChannelManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java index be6b9f046..4db9f7f6c 100644 --- a/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java +++ b/push-notifications/android/src/main/java/com/capacitorjs/plugins/pushnotifications/NotificationChannelManager.java @@ -13,7 +13,6 @@ import com.getcapacitor.Logger; import com.getcapacitor.PluginCall; import com.getcapacitor.util.WebColor; - import java.util.List; public class NotificationChannelManager { From c1d001f561b35b92ff8e874753c5c60b80781eb2 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 17:24:45 -0600 Subject: [PATCH 29/36] Removing Firebase SDK --- push-notifications/README.md | 2 +- .../ios/Plugin/PushNotificationsPlugin.swift | 7 +------ push-notifications/ios/Podfile | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index 888a93017..3fde2a789 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -373,7 +373,7 @@ Remove all native listeners for this plugin. | **`importance`** | 1 \| 2 \| 5 \| 4 \| 3 | The level of interruption for notifications posted to this channel. | 1.0.0 | | **`visibility`** | 0 \| 1 \| -1 | The visibility of notifications posted to this channel. This setting is for whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.0.0 | | **`lights`** | boolean | Whether notifications posted to this channel should display notification lights, on devices that support it. | 1.0.0 | -| **`lightColor`** | string | The light color for notifications posted to this channel. Only supported if lights are enabled on this channel and the device supports it. Supported color formats are `#RRGGBB` and `#AARRGGBB`. | 1.0.0 | +| **`lightColor`** | string | The light color for notifications posted to this channel. Only supported if lights are enabled on this channel and the device supports it. Supported color formats are `#RRGGBB` and `#RRGGBBAA`. | 1.0.0 | | **`vibration`** | boolean | Whether notifications posted to this channel should vibrate. | 1.0.0 | diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index be50c968e..9561594bd 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -1,7 +1,5 @@ import Foundation import Capacitor -import FirebaseCore -import FirebaseMessaging import UserNotifications enum PushNotificationError: Error { @@ -21,7 +19,6 @@ public class PushNotificationsPlugin: CAPPlugin { private var appDelegateRegistrationCalled: Bool = false override public func load() { - FirebaseApp.configure() self.bridge?.notificationRouter.pushNotificationHandler = self.notificationDelegateHandler self.notificationDelegateHandler.plugin = self @@ -159,13 +156,11 @@ public class PushNotificationsPlugin: CAPPlugin { @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification) { appDelegateRegistrationCalled = true if let deviceToken = notification.object as? Data { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) - Messaging.messaging().apnsToken = deviceToken + let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) notifyListeners("registration", data: [ "value": deviceTokenString ]) } else if let stringToken = notification.object as? String { - Messaging.messaging().apnsToken = stringToken.data(using: .utf8) notifyListeners("registration", data: [ "value": stringToken ]) diff --git a/push-notifications/ios/Podfile b/push-notifications/ios/Podfile index 0a4549460..80322bf8a 100644 --- a/push-notifications/ios/Podfile +++ b/push-notifications/ios/Podfile @@ -9,7 +9,6 @@ end target 'Plugin' do capacitor_pods - pod 'Firebase/Messaging', '~> 7.3' end target 'PluginTests' do From 0350a63308d832c3c0cf53d639979abb71e6e5ef Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 17:25:06 -0600 Subject: [PATCH 30/36] Upgrading plugins to alpha.12 --- push-notifications/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/push-notifications/package.json b/push-notifications/package.json index 819514f6b..92f724b26 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -43,10 +43,10 @@ "prepublishOnly": "npm run build" }, "devDependencies": { - "@capacitor/android": "^3.0.0-alpha.11", - "@capacitor/core": "^3.0.0-alpha.9", + "@capacitor/android": "^3.0.0-alpha.12", + "@capacitor/core": "^3.0.0-alpha.12", "@capacitor/docgen": "^0.0.10", - "@capacitor/ios": "^3.0.0-alpha.11", + "@capacitor/ios": "^3.0.0-alpha.12", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^1.0.1", "@ionic/swiftlint-config": "^1.1.2", From 3cbe02d373ac5bd3586caac6ba41000fbd813180 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 17:57:17 -0600 Subject: [PATCH 31/36] Upgrading minimum iOS version --- .../ios/Plugin.xcodeproj/project.pbxproj | 30 +++++-------------- .../xcshareddata/xcschemes/Plugin.xcscheme | 2 +- .../xcschemes/PluginTests.xcscheme | 2 +- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj index cc3f929e0..a5e5dc4a8 100644 --- a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj +++ b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj @@ -193,7 +193,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1160; + LastUpgradeCheck = 1230; ORGANIZATIONNAME = "Max Lynch"; TargetAttributes = { 50ADFF87201F53D600D50D53 = { @@ -272,29 +272,11 @@ "${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Capacitor/Capacitor.framework", "${BUILT_PRODUCTS_DIR}/CapacitorCordova/Cordova.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseInstanceID/FirebaseInstanceID.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseMessaging/FirebaseMessaging.framework", - "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", - "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", - "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", - "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Capacitor.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cordova.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstanceID.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseMessaging.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -376,6 +358,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -406,7 +389,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -442,6 +425,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -466,7 +450,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -489,7 +473,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Plugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)"; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; @@ -514,7 +498,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Plugin/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; diff --git a/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme index 303f2621b..901886c9b 100644 --- a/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme +++ b/push-notifications/ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 8 Jan 2021 17:59:42 -0600 Subject: [PATCH 32/36] lint --- push-notifications/ios/Plugin/PushNotificationsPlugin.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index 9561594bd..ca8af177b 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -156,7 +156,7 @@ public class PushNotificationsPlugin: CAPPlugin { @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification) { appDelegateRegistrationCalled = true if let deviceToken = notification.object as? Data { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) + let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) notifyListeners("registration", data: [ "value": deviceTokenString ]) From 8be3dbdb6cb7521b243feb241d8202df3928e175 Mon Sep 17 00:00:00 2001 From: Joseph Pender Date: Fri, 8 Jan 2021 18:11:36 -0600 Subject: [PATCH 33/36] Renaming PushNotificationsDelegate to PushNotificationsHandler --- push-notifications/ios/Plugin.xcodeproj/project.pbxproj | 8 ++++---- ...tionsDelegate.swift => PushNotificationsHandler.swift} | 4 ++-- .../ios/Plugin/PushNotificationsPlugin.swift | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) rename push-notifications/ios/Plugin/{PushNotificationsDelegate.swift => PushNotificationsHandler.swift} (96%) diff --git a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj index a5e5dc4a8..959c6f5c3 100644 --- a/push-notifications/ios/Plugin.xcodeproj/project.pbxproj +++ b/push-notifications/ios/Plugin.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFFA52020D75100D50D53 /* Capacitor.framework */; }; 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */; }; 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E1A94720377CB70090CE1A /* PushNotificationsPlugin.swift */; }; - CFA171D7258422F200F2DED2 /* PushNotificationsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */; }; + CFA171D7258422F200F2DED2 /* PushNotificationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA171D6258422F200F2DED2 /* PushNotificationsHandler.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,7 +42,7 @@ 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.debug.xcconfig"; sourceTree = ""; }; 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.release.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.release.xcconfig"; sourceTree = ""; }; 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.debug.xcconfig"; sourceTree = ""; }; - CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsDelegate.swift; sourceTree = ""; }; + CFA171D6258422F200F2DED2 /* PushNotificationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsHandler.swift; sourceTree = ""; }; CFDB66F62587D6E400732657 /* FirebaseMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.release.xcconfig"; sourceTree = ""; }; F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -97,7 +97,7 @@ 50ADFF8B201F53D600D50D53 /* PushNotificationsPlugin.h */, 50ADFFA72020EE4F00D50D53 /* PushNotificationsPlugin.m */, 50ADFF8C201F53D600D50D53 /* Info.plist */, - CFA171D6258422F200F2DED2 /* PushNotificationsDelegate.swift */, + CFA171D6258422F200F2DED2 /* PushNotificationsHandler.swift */, ); path = Plugin; sourceTree = ""; @@ -310,7 +310,7 @@ files = ( 50E1A94820377CB70090CE1A /* PushNotificationsPlugin.swift in Sources */, 50ADFFA82020EE4F00D50D53 /* PushNotificationsPlugin.m in Sources */, - CFA171D7258422F200F2DED2 /* PushNotificationsDelegate.swift in Sources */, + CFA171D7258422F200F2DED2 /* PushNotificationsHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift b/push-notifications/ios/Plugin/PushNotificationsHandler.swift similarity index 96% rename from push-notifications/ios/Plugin/PushNotificationsDelegate.swift rename to push-notifications/ios/Plugin/PushNotificationsHandler.swift index aa11e9b3b..372a426c6 100644 --- a/push-notifications/ios/Plugin/PushNotificationsDelegate.swift +++ b/push-notifications/ios/Plugin/PushNotificationsHandler.swift @@ -1,8 +1,8 @@ import Capacitor import UserNotifications -public class PushNotificationsDelegate: NSObject, NotificationHandlerProtocol { - public var plugin: CAPPlugin? +public class PushNotificationsHandler: NSObject, NotificationHandlerProtocol { + public weak var plugin: CAPPlugin? var notificationRequestLookup = [String: JSObject]() public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) { diff --git a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift index ca8af177b..8a24756d9 100644 --- a/push-notifications/ios/Plugin/PushNotificationsPlugin.swift +++ b/push-notifications/ios/Plugin/PushNotificationsPlugin.swift @@ -15,7 +15,7 @@ enum PushNotificationsPermissions: String { @objc(PushNotificationsPlugin) public class PushNotificationsPlugin: CAPPlugin { - private let notificationDelegateHandler = PushNotificationsDelegate() + private let notificationDelegateHandler = PushNotificationsHandler() private var appDelegateRegistrationCalled: Bool = false override public func load() { @@ -24,12 +24,12 @@ public class PushNotificationsPlugin: CAPPlugin { NotificationCenter.default.addObserver(self, selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), - name: Notification.Name(Notification.Name.capacitorDidRegisterForRemoteNotifications.self.rawValue), + name: .capacitorDidRegisterForRemoteNotifications, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), - name: Notification.Name(Notification.Name.capacitorDidFailToRegisterForRemoteNotifications.self.rawValue), + name: .capacitorDidFailToRegisterForRemoteNotifications, object: nil) } From c1f0721a39857089c30e15e79a3d3144b6252f75 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 8 Jan 2021 16:22:13 -0800 Subject: [PATCH 34/36] version changes --- push-notifications/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/push-notifications/package.json b/push-notifications/package.json index 92f724b26..34a73957e 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -45,21 +45,21 @@ "devDependencies": { "@capacitor/android": "^3.0.0-alpha.12", "@capacitor/core": "^3.0.0-alpha.12", - "@capacitor/docgen": "^0.0.10", + "@capacitor/docgen": "0.0.14", "@capacitor/ios": "^3.0.0-alpha.12", "@ionic/eslint-config": "^0.3.0", - "@ionic/prettier-config": "^1.0.1", + "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", "eslint": "^7.11.0", "prettier": "~2.2.0", "prettier-plugin-java": "~1.0.0", - "rimraf": "^3.0.2", - "rollup": "^2.32.0", + "rimraf": "^3.0.0", + "rollup": "^2.29.0", "swiftlint": "^1.0.1", "typescript": "~4.0.3" }, "peerDependencies": { - "@capacitor/core": "next" + "@capacitor/core": "^3.0.0-alpha.12" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", From 989d2b3a00790ce94f21f0e17c1f2d0a20b5461f Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 8 Jan 2021 16:33:33 -0800 Subject: [PATCH 35/36] regen docs --- push-notifications/README.md | 39 ++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/push-notifications/README.md b/push-notifications/README.md index 3fde2a789..93485646b 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -79,12 +79,13 @@ An empty Array can be provided if none of the previous options are desired. `pus * [`listChannels()`](#listchannels) * [`checkPermissions()`](#checkpermissions) * [`requestPermissions()`](#requestpermissions) -* [`addListener(...)`](#addlistener) -* [`addListener(...)`](#addlistener) -* [`addListener(...)`](#addlistener) -* [`addListener(...)`](#addlistener) +* [`addListener('registration', ...)`](#addlistenerregistration-) +* [`addListener('registrationError', ...)`](#addlistenerregistrationerror-) +* [`addListener('pushNotificationReceived', ...)`](#addlistenerpushnotificationreceived-) +* [`addListener('pushNotificationActionPerformed', ...)`](#addlistenerpushnotificationactionperformed-) * [`removeAllListeners()`](#removealllisteners) * [Interfaces](#interfaces) +* [Type Aliases](#type-aliases) @@ -238,7 +239,7 @@ Request permission to receive push notifications. -------------------- -### addListener(...) +### addListener('registration', ...) ```typescript addListener(eventName: 'registration', listenerFunc: (token: PushNotificationToken) => void) => PluginListenerHandle @@ -250,7 +251,7 @@ Provides the push notification token. | Param | Type | | ------------------ | ------------------------------------------------------------------------------------------- | -| **`eventName`** | "registration" | +| **`eventName`** | 'registration' | | **`listenerFunc`** | (token: PushNotificationToken) => void | **Returns:** PluginListenerHandle @@ -260,7 +261,7 @@ Provides the push notification token. -------------------- -### addListener(...) +### addListener('registrationError', ...) ```typescript addListener(eventName: 'registrationError', listenerFunc: (error: any) => void) => PluginListenerHandle @@ -272,7 +273,7 @@ Provides an error with the registration problem. | Param | Type | | ------------------ | ------------------------------------ | -| **`eventName`** | "registrationError" | +| **`eventName`** | 'registrationError' | | **`listenerFunc`** | (error: any) => void | **Returns:** PluginListenerHandle @@ -282,7 +283,7 @@ Provides an error with the registration problem. -------------------- -### addListener(...) +### addListener('pushNotificationReceived', ...) ```typescript addListener(eventName: 'pushNotificationReceived', listenerFunc: (notification: PushNotificationSchema) => void) => PluginListenerHandle @@ -292,7 +293,7 @@ Called when the device receives a push notification. | Param | Type | | ------------------ | ---------------------------------------------------------------------------------------------------- | -| **`eventName`** | "pushNotificationReceived" | +| **`eventName`** | 'pushNotificationReceived' | | **`listenerFunc`** | (notification: PushNotificationSchema) => void | **Returns:** PluginListenerHandle @@ -302,7 +303,7 @@ Called when the device receives a push notification. -------------------- -### addListener(...) +### addListener('pushNotificationActionPerformed', ...) ```typescript addListener(eventName: 'pushNotificationActionPerformed', listenerFunc: (notification: PushNotificationActionPerformed) => void) => PluginListenerHandle @@ -312,7 +313,7 @@ Called when an action is performed on a push notification. | Param | Type | | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | -| **`eventName`** | "pushNotificationActionPerformed" | +| **`eventName`** | 'pushNotificationActionPerformed' | | **`listenerFunc`** | (notification: PushNotificationActionPerformed) => void | **Returns:** PluginListenerHandle @@ -386,9 +387,9 @@ Remove all native listeners for this plugin. #### PermissionStatus -| Prop | Type | Since | -| ------------- | ------------------------------------------------------------------------- | ----- | -| **`receive`** | "prompt" \| "prompt-with-rationale" \| "granted" \| "denied" | 1.0.0 | +| Prop | Type | Since | +| ------------- | ----------------------------------------------------------- | ----- | +| **`receive`** | PermissionState | 1.0.0 | #### PluginListenerHandle @@ -413,4 +414,12 @@ Remove all native listeners for this plugin. | **`inputValue`** | string | 1.0.0 | | **`notification`** | PushNotificationSchema | 1.0.0 | + +### Type Aliases + + +#### PermissionState + +'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' + From b28993d133a82f534af63362247a2b17784f98b4 Mon Sep 17 00:00:00 2001 From: Daniel Imhoff Date: Fri, 8 Jan 2021 16:34:12 -0800 Subject: [PATCH 36/36] add docs for cli hints --- push-notifications/package.json | 1 + push-notifications/src/definitions.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/push-notifications/package.json b/push-notifications/package.json index 34a73957e..7f5780666 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -44,6 +44,7 @@ }, "devDependencies": { "@capacitor/android": "^3.0.0-alpha.12", + "@capacitor/cli": "^3.0.0-alpha.12", "@capacitor/core": "^3.0.0-alpha.12", "@capacitor/docgen": "0.0.14", "@capacitor/ios": "^3.0.0-alpha.12", diff --git a/push-notifications/src/definitions.ts b/push-notifications/src/definitions.ts index 5ced24c7d..2029271c0 100644 --- a/push-notifications/src/definitions.ts +++ b/push-notifications/src/definitions.ts @@ -1,5 +1,31 @@ +/// + import type { PermissionState, PluginListenerHandle } from '@capacitor/core'; +export type PresentationOption = 'badge' | 'sound' | 'alert'; + +declare module '@capacitor/cli' { + export interface PluginsConfig { + PushNotifications?: { + /** + * Configure the way push notifications are displayed when the app is in foreground. + * + * This is an array of strings you can combine. Possible values in the array are: + * - `badge`: badge count on the app icon is updated (default value) + * - `sound`: the device will ring/vibrate when the push notification is received + * - `alert`: the push notification is displayed in a native dialog + * + * An empty array can be provided if none of the options are desired. + * + * Only available for iOS. + * + * @since 1.0.0 + */ + presentationOptions: PresentationOption[]; + }; + } +} + export interface PushNotificationsPlugin { /** * Register the app to receive push notifications.