diff options
author | Hailey <me@haileyok.com> | 2024-05-15 11:49:07 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-15 11:49:07 -0700 |
commit | bf7b66d5c1c0d7f7bdcc1c1aa43b6881d797d1e8 (patch) | |
tree | dfbbd3babc73b4251883b8dc786b1dbea7f08c1a /modules | |
parent | 31868b255f7be001821e03033b3bdd1070ea28cf (diff) | |
download | voidsky-bf7b66d5c1c0d7f7bdcc1c1aa43b6881d797d1e8.tar.zst |
Add push notification extensions (#4005)
* add wav * add sound to config * add extension to `updateExtensions.sh` * add ios source files * add a build extension * add a new module * use correct type on ios * update the build plugin * add android handler * create a patch for expo-notifications * basic android implementation * add entitlements for notifications extension * add some generic logic for ios * add age check logic * add extension to app config * remove dash * move directory * rename again * update privacy manifest * add prefs storage ios * better types * create interface for setting and getting prefs * add notifications prefs for android * add functions to module * add types to js * add prefs context * add web stub * wrap the app * fix types * more preferences for ios * add a test toggle * swap vars * update patch * fix patch error * fix typo * sigh * sigh * get stored prefs on launch * anotehr type * simplify * about finished * comment * adjust plugin * use supported file types * update NSE * futureproof ios * futureproof android * update sound file name * handle initialization * more cleanup * update js types * strict js types * set the notification channel * rm * add silent channel * add mute logic * update patch * podfile * adjust channels * fix android channel * update readme * oreo or higher * nit * don't use getValue * nit
Diffstat (limited to 'modules')
19 files changed, 730 insertions, 2 deletions
diff --git a/modules/BlueskyNSE/BlueskyNSE.entitlements b/modules/BlueskyNSE/BlueskyNSE.entitlements new file mode 100644 index 000000000..4954bdb33 --- /dev/null +++ b/modules/BlueskyNSE/BlueskyNSE.entitlements @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>com.apple.security.application-groups</key> + <array> + <string>group.app.bsky</string> + </array> + </dict> +</plist> \ No newline at end of file diff --git a/modules/BlueskyNSE/Info.plist b/modules/BlueskyNSE/Info.plist new file mode 100644 index 000000000..c2dd7eda6 --- /dev/null +++ b/modules/BlueskyNSE/Info.plist @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>NSExtension</key> + <dict> + <key>NSExtensionPointIdentifier</key> + <string>com.apple.usernotifications.service</string> + <key>NSExtensionPrincipalClass</key> + <string>$(PRODUCT_MODULE_NAME).NotificationService</string> + </dict> + <key>MainAppScheme</key> + <string>bluesky</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundleDisplayName</key> + <string>Bluesky Notifications</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleVersion</key> + <string>$(CURRENT_PROJECT_VERSION)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundlePackageType</key> + <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> + <key>CFBundleShortVersionString</key> + <string>$(MARKETING_VERSION)</string> + </dict> +</plist> \ No newline at end of file diff --git a/modules/BlueskyNSE/NotificationService.swift b/modules/BlueskyNSE/NotificationService.swift new file mode 100644 index 000000000..c6f391e00 --- /dev/null +++ b/modules/BlueskyNSE/NotificationService.swift @@ -0,0 +1,51 @@ +import UserNotifications + +let APP_GROUP = "group.app.bsky" + +class NotificationService: UNNotificationServiceExtension { + var prefs = UserDefaults(suiteName: APP_GROUP) + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + guard var bestAttempt = createCopy(request.content), + let reason = request.content.userInfo["reason"] as? String + else { + contentHandler(request.content) + return + } + + if reason == "chat-message" { + mutateWithChatMessage(bestAttempt) + } + + // The badge should always be incremented when in the background + mutateWithBadge(bestAttempt) + + contentHandler(bestAttempt) + } + + override func serviceExtensionTimeWillExpire() { + // If for some reason the alloted time expires, we don't actually want to display a notification + } + + func createCopy(_ content: UNNotificationContent) -> UNMutableNotificationContent? { + return content.mutableCopy() as? UNMutableNotificationContent + } + + func mutateWithBadge(_ content: UNMutableNotificationContent) { + content.badge = 1 + } + + func mutateWithChatMessage(_ content: UNMutableNotificationContent) { + if self.prefs?.bool(forKey: "playSoundChat") == true { + mutateWithDmSound(content) + } + } + + func mutateWithDefaultSound(_ content: UNMutableNotificationContent) { + content.sound = UNNotificationSound.default + } + + func mutateWithDmSound(_ content: UNMutableNotificationContent) { + content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "dm.aiff")) + } +} diff --git a/modules/Share-with-Bluesky/Info.plist b/modules/Share-with-Bluesky/Info.plist index 90fe92345..421abb3c4 100644 --- a/modules/Share-with-Bluesky/Info.plist +++ b/modules/Share-with-Bluesky/Info.plist @@ -38,4 +38,4 @@ <key>CFBundleShortVersionString</key> <string>$(MARKETING_VERSION)</string> </dict> -</plist> +</plist> \ No newline at end of file diff --git a/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements b/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements index d2253d31f..4954bdb33 100644 --- a/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements +++ b/modules/Share-with-Bluesky/Share-with-Bluesky.entitlements @@ -7,4 +7,4 @@ <string>group.app.bsky</string> </array> </dict> -</plist> +</plist> \ No newline at end of file diff --git a/modules/expo-background-notification-handler/android/build.gradle b/modules/expo-background-notification-handler/android/build.gradle new file mode 100644 index 000000000..e18eee934 --- /dev/null +++ b/modules/expo-background-notification-handler/android/build.gradle @@ -0,0 +1,93 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'expo.modules.backgroundnotificationhandler' +version = '0.5.0' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 33) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } + } + + namespace "expo.modules.backgroundnotificationhandler" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.5.0" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + implementation 'com.google.firebase:firebase-messaging-ktx:24.0.0' +} diff --git a/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml b/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..bdae66c8f --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ +<manifest> +</manifest> diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt new file mode 100644 index 000000000..344508523 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt @@ -0,0 +1,39 @@ +package expo.modules.backgroundnotificationhandler + +import android.content.Context +import com.google.firebase.messaging.RemoteMessage + +class BackgroundNotificationHandler( + private val context: Context, + private val notifInterface: BackgroundNotificationHandlerInterface +) { + fun handleMessage(remoteMessage: RemoteMessage) { + if (ExpoBackgroundNotificationHandlerModule.isForegrounded) { + // We'll let expo-notifications handle the notification if the app is foregrounded + return + } + + if (remoteMessage.data["reason"] == "chat-message") { + mutateWithChatMessage(remoteMessage) + } + + notifInterface.showMessage(remoteMessage) + } + + private fun mutateWithChatMessage(remoteMessage: RemoteMessage) { + if (NotificationPrefs(context).getBoolean("playSoundChat")) { + // If oreo or higher + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + remoteMessage.data["channelId"] = "chat-messages" + } else { + remoteMessage.data["sound"] = "dm.mp3" + } + } else { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + remoteMessage.data["channelId"] = "chat-messages-muted" + } else { + remoteMessage.data["sound"] = null + } + } + } +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt new file mode 100644 index 000000000..41fb65eb6 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandlerInterface.kt @@ -0,0 +1,7 @@ +package expo.modules.backgroundnotificationhandler + +import com.google.firebase.messaging.RemoteMessage + +interface BackgroundNotificationHandlerInterface { + fun showMessage(remoteMessage: RemoteMessage) +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt new file mode 100644 index 000000000..083ff1223 --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/ExpoBackgroundNotificationHandlerModule.kt @@ -0,0 +1,70 @@ +package expo.modules.backgroundnotificationhandler + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBackgroundNotificationHandlerModule : Module() { + companion object { + var isForegrounded = false + } + + override fun definition() = ModuleDefinition { + Name("ExpoBackgroundNotificationHandler") + + OnCreate { + NotificationPrefs(appContext.reactContext).initialize() + } + + OnActivityEntersForeground { + isForegrounded = true + } + + OnActivityEntersBackground { + isForegrounded = false + } + + AsyncFunction("getAllPrefsAsync") { + return@AsyncFunction NotificationPrefs(appContext.reactContext).getAllPrefs() + } + + AsyncFunction("getBoolAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getBoolean(forKey) + } + + AsyncFunction("getStringAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getString(forKey) + } + + AsyncFunction("getStringArrayAsync") { forKey: String -> + return@AsyncFunction NotificationPrefs(appContext.reactContext).getStringArray(forKey) + } + + AsyncFunction("setBoolAsync") { forKey: String, value: Boolean -> + NotificationPrefs(appContext.reactContext).setBoolean(forKey, value) + } + + AsyncFunction("setStringAsync") { forKey: String, value: String -> + NotificationPrefs(appContext.reactContext).setString(forKey, value) + } + + AsyncFunction("setStringArrayAsync") { forKey: String, value: Array<String> -> + NotificationPrefs(appContext.reactContext).setStringArray(forKey, value) + } + + AsyncFunction("addToStringArrayAsync") { forKey: String, string: String -> + NotificationPrefs(appContext.reactContext).addToStringArray(forKey, string) + } + + AsyncFunction("removeFromStringArrayAsync") { forKey: String, string: String -> + NotificationPrefs(appContext.reactContext).removeFromStringArray(forKey, string) + } + + AsyncFunction("addManyToStringArrayAsync") { forKey: String, strings: Array<String> -> + NotificationPrefs(appContext.reactContext).addManyToStringArray(forKey, strings) + } + + AsyncFunction("removeManyFromStringArrayAsync") { forKey: String, strings: Array<String> -> + NotificationPrefs(appContext.reactContext).removeManyFromStringArray(forKey, strings) + } + } +} diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt new file mode 100644 index 000000000..17ef9205e --- /dev/null +++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt @@ -0,0 +1,134 @@ +package expo.modules.backgroundnotificationhandler + +import android.content.Context + +val DEFAULTS = mapOf<String, Any>( + "playSoundChat" to true, + "playSoundFollow" to false, + "playSoundLike" to false, + "playSoundMention" to false, + "playSoundQuote" to false, + "playSoundReply" to false, + "playSoundRepost" to false, + "mutedThreads" to mapOf<String, List<String>>() +) + +class NotificationPrefs (private val context: Context?) { + private val prefs = context?.getSharedPreferences("xyz.blueskyweb.app", Context.MODE_PRIVATE) + ?: throw Error("Context is null") + + fun initialize() { + prefs + .edit() + .apply { + DEFAULTS.forEach { (key, value) -> + if (prefs.contains(key)) { + return@forEach + } + + when (value) { + is Boolean -> { + putBoolean(key, value) + } + is String -> { + putString(key, value) + } + is Array<*> -> { + putStringSet(key, value.map { it.toString() }.toSet()) + } + is Map<*, *> -> { + putStringSet(key, value.map { it.toString() }.toSet()) + } + } + } + } + .apply() + } + + fun getAllPrefs(): MutableMap<String, *> { + return prefs.all + } + + fun getBoolean(key: String): Boolean { + return prefs.getBoolean(key, false) + } + + fun getString(key: String): String? { + return prefs.getString(key, null) + } + + fun getStringArray(key: String): Array<String>? { + return prefs.getStringSet(key, null)?.toTypedArray() + } + + fun setBoolean(key: String, value: Boolean) { + prefs + .edit() + .apply { + putBoolean(key, value) + } + .apply() + } + + fun setString(key: String, value: String) { + prefs + .edit() + .apply { + putString(key, value) + } + .apply() + } + + fun setStringArray(key: String, value: Array<String>) { + prefs + .edit() + .apply { + putStringSet(key, value.toSet()) + } + .apply() + } + + fun addToStringArray(key: String, string: String) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.add(string) + putStringSet(key, set) + } + .apply() + } + + fun removeFromStringArray(key: String, string: String) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.remove(string) + putStringSet(key, set) + } + .apply() + } + + fun addManyToStringArray(key: String, strings: Array<String>) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.addAll(strings.toSet()) + putStringSet(key, set) + } + .apply() + } + + fun removeManyFromStringArray(key: String, strings: Array<String>) { + prefs + .edit() + .apply { + val set = prefs.getStringSet(key, null)?.toMutableSet() ?: mutableSetOf() + set.removeAll(strings.toSet()) + putStringSet(key, set) + } + .apply() + } +} \ No newline at end of file diff --git a/modules/expo-background-notification-handler/expo-module.config.json b/modules/expo-background-notification-handler/expo-module.config.json new file mode 100644 index 000000000..9e5c9d550 --- /dev/null +++ b/modules/expo-background-notification-handler/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["ios", "android"], + "ios": { + "modules": ["ExpoBackgroundNotificationHandlerModule"] + }, + "android": { + "modules": ["expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule"] + } +} diff --git a/modules/expo-background-notification-handler/index.ts b/modules/expo-background-notification-handler/index.ts new file mode 100644 index 000000000..680c6c13f --- /dev/null +++ b/modules/expo-background-notification-handler/index.ts @@ -0,0 +1,2 @@ +import {BackgroundNotificationHandler} from './src/ExpoBackgroundNotificationHandlerModule' +export default BackgroundNotificationHandler diff --git a/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec new file mode 100644 index 000000000..363c7b5e6 --- /dev/null +++ b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandler.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'ExpoBackgroundNotificationHandler' + s.version = '1.0.0' + s.summary = 'Interface for BlueskyNSE preferences' + s.description = 'Interface for BlueskyNSE preferenes' + s.author = '' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift new file mode 100644 index 000000000..08972a04c --- /dev/null +++ b/modules/expo-background-notification-handler/ios/ExpoBackgroundNotificationHandlerModule.swift @@ -0,0 +1,116 @@ +import ExpoModulesCore + +let APP_GROUP = "group.app.bsky" + +let DEFAULTS: [String:Any] = [ + "playSoundChat" : true, + "playSoundFollow": false, + "playSoundLike": false, + "playSoundMention": false, + "playSoundQuote": false, + "playSoundReply": false, + "playSoundRepost": false, + "mutedThreads": [:] as! [String:[String]] +] + +/* + * The purpose of this module is to store values that are needed by the notification service + * extension. Since we would rather get and store values such as age or user mute state + * while the app is foregrounded, we should use this module liberally. We should aim to keep + * background fetches to a minimum (two or three times per hour) while the app is backgrounded + * or killed + */ +public class ExpoBackgroundNotificationHandlerModule: Module { + let userDefaults = UserDefaults(suiteName: APP_GROUP) + + public func definition() -> ModuleDefinition { + Name("ExpoBackgroundNotificationHandler") + + OnCreate { + DEFAULTS.forEach { p in + if userDefaults?.value(forKey: p.key) == nil { + userDefaults?.setValue(p.value, forKey: p.key) + } + } + } + + AsyncFunction("getAllPrefsAsync") { () -> [String:Any]? in + var keys: [String] = [] + DEFAULTS.forEach { p in + keys.append(p.key) + } + return userDefaults?.dictionaryWithValues(forKeys: keys) + } + + AsyncFunction("getBoolAsync") { (forKey: String) -> Bool in + if let pref = userDefaults?.bool(forKey: forKey) { + return pref + } + return false + } + + AsyncFunction("getStringAsync") { (forKey: String) -> String? in + if let pref = userDefaults?.string(forKey: forKey) { + return pref + } + return nil + } + + AsyncFunction("getStringArrayAsync") { (forKey: String) -> [String]? in + if let pref = userDefaults?.stringArray(forKey: forKey) { + return pref + } + return nil + } + + AsyncFunction("setBoolAsync") { (forKey: String, value: Bool) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("setStringAsync") { (forKey: String, value: String) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("setStringArrayAsync") { (forKey: String, value: [String]) -> Void in + userDefaults?.setValue(value, forKey: forKey) + } + + AsyncFunction("addToStringArrayAsync") { (forKey: String, string: String) in + if var curr = userDefaults?.stringArray(forKey: forKey), + !curr.contains(string) + { + curr.append(string) + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("removeFromStringArrayAsync") { (forKey: String, string: String) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + curr.removeAll { s in + return s == string + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("addManyToStringArrayAsync") { (forKey: String, strings: [String]) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + strings.forEach { s in + if !curr.contains(s) { + curr.append(s) + } + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + + AsyncFunction("removeManyFromStringArrayAsync") { (forKey: String, strings: [String]) in + if var curr = userDefaults?.stringArray(forKey: forKey) { + strings.forEach { s in + curr.removeAll(where: { $0 == s }) + } + userDefaults?.setValue(curr, forKey: forKey) + } + } + } +} diff --git a/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx new file mode 100644 index 000000000..6ecdd1d47 --- /dev/null +++ b/modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider.tsx @@ -0,0 +1,70 @@ +import React from 'react' + +import {BackgroundNotificationHandlerPreferences} from './ExpoBackgroundNotificationHandler.types' +import {BackgroundNotificationHandler} from './ExpoBackgroundNotificationHandlerModule' + +interface BackgroundNotificationPreferencesContext { + preferences: BackgroundNotificationHandlerPreferences + setPref: <Key extends keyof BackgroundNotificationHandlerPreferences>( + key: Key, + value: BackgroundNotificationHandlerPreferences[Key], + ) => void +} + +const Context = React.createContext<BackgroundNotificationPreferencesContext>( + {} as BackgroundNotificationPreferencesContext, +) +export const useBackgroundNotificationPreferences = () => + React.useContext(Context) + +export function BackgroundNotificationPreferencesProvider({ + children, +}: { + children: React.ReactNode +}) { + const [preferences, setPreferences] = + React.useState<BackgroundNotificationHandlerPreferences>({ + playSoundChat: true, + }) + + React.useEffect(() => { + ;(async () => { + const prefs = await BackgroundNotificationHandler.getAllPrefsAsync() + setPreferences(prefs) + })() + }, []) + + const value = React.useMemo( + () => ({ + preferences, + setPref: async < + Key extends keyof BackgroundNotificationHandlerPreferences, + >( + k: Key, + v: BackgroundNotificationHandlerPreferences[Key], + ) => { + switch (typeof v) { + case 'boolean': { + await BackgroundNotificationHandler.setBoolAsync(k, v) + break + } + case 'string': { + await BackgroundNotificationHandler.setStringAsync(k, v) + break + } + default: { + throw new Error(`Invalid type for value: ${typeof v}`) + } + } + + setPreferences(prev => ({ + ...prev, + [k]: v, + })) + }, + }), + [preferences], + ) + + return <Context.Provider value={value}>{children}</Context.Provider> +} diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts new file mode 100644 index 000000000..5fbd302da --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandler.types.ts @@ -0,0 +1,40 @@ +export type ExpoBackgroundNotificationHandlerModule = { + getAllPrefsAsync: () => Promise<BackgroundNotificationHandlerPreferences> + getBoolAsync: (forKey: string) => Promise<boolean> + getStringAsync: (forKey: string) => Promise<string> + getStringArrayAsync: (forKey: string) => Promise<string[]> + setBoolAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: boolean, + ) => Promise<void> + setStringAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise<void> + setStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise<void> + addToStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise<void> + removeFromStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string, + ) => Promise<void> + addManyToStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise<void> + removeManyFromStringArrayAsync: ( + forKey: keyof BackgroundNotificationHandlerPreferences, + value: string[], + ) => Promise<void> +} + +// TODO there are more preferences in the native code, however they have not been added here yet. +// Don't add them until the native logic also handles the notifications for those preference types. +export type BackgroundNotificationHandlerPreferences = { + playSoundChat: boolean +} diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts new file mode 100644 index 000000000..d6517893a --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.ts @@ -0,0 +1,8 @@ +import {requireNativeModule} from 'expo-modules-core' + +import {ExpoBackgroundNotificationHandlerModule} from './ExpoBackgroundNotificationHandler.types' + +export const BackgroundNotificationHandler = + requireNativeModule<ExpoBackgroundNotificationHandlerModule>( + 'ExpoBackgroundNotificationHandler', + ) diff --git a/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts new file mode 100644 index 000000000..29e27fd0f --- /dev/null +++ b/modules/expo-background-notification-handler/src/ExpoBackgroundNotificationHandlerModule.web.ts @@ -0,0 +1,27 @@ +import { + BackgroundNotificationHandlerPreferences, + ExpoBackgroundNotificationHandlerModule, +} from './ExpoBackgroundNotificationHandler.types' + +// Stub for web +export const BackgroundNotificationHandler = { + getAllPrefsAsync: async () => { + return {} as BackgroundNotificationHandlerPreferences + }, + getBoolAsync: async (_: string) => { + return false + }, + getStringAsync: async (_: string) => { + return '' + }, + getStringArrayAsync: async (_: string) => { + return [] + }, + setBoolAsync: async (_: string, __: boolean) => {}, + setStringAsync: async (_: string, __: string) => {}, + setStringArrayAsync: async (_: string, __: string[]) => {}, + addToStringArrayAsync: async (_: string, __: string) => {}, + removeFromStringArrayAsync: async (_: string, __: string) => {}, + addManyToStringArrayAsync: async (_: string, __: string[]) => {}, + removeManyFromStringArrayAsync: async (_: string, __: string[]) => {}, +} as ExpoBackgroundNotificationHandlerModule |