From 544f7befe0f7d3e61fb03365ec588a2ab3c5a17a Mon Sep 17 00:00:00 2001 From: hailey Date: Fri, 2 May 2025 13:23:39 -0700 Subject: bump it bop it upgrade it (rn 79/expo 53) (#8281) * basic bumps * more tweaking * fix rn patch * fix crop picker patch * fix media library patch * rm unnecessary patch * fix notifications patch * update bottomsheet * Update withAppDelegateReferrer.js * Delete withNoBundleCompression.js * rm withNoBundleCompression plugin * rm findLast shim * metro package exports is enabled by default * update react/react-dom/react-compiler * fix reanimated issue * vendor expo-ized emoji popup * fix types * hackfix view full thread * Update EmojiPickerModule.podspec * more upgrades * fix multiformats package version * add baseurl * bump mmkv * bumps * update react-keyed-flatten-children * bump locale packages * fix emoji picker dark mode * rn upgrades * Revert "bump locale packages" This reverts commit fc82f0f173032127dd7c18ed0316ae26f53db51d. * upgrade testing-library * rm test renderer * update patch name minors * rm findNodeHandle from tabbar * only do scrollview tag thing on ios * disable package exports * update expo notifications handler * memoize emoji picker styles * fix tests, mock multiformats * bump some dev deps with RC versions * completely rearchitect toasts * rm logs * layout animation config for composer footer * disable autolinking for patched libs * undo lingui changes * version bump from release candidate to 0.1 * update atproto deps * rm @did-plc/server * fix key issue (maybe) * move URL polyfill to the polyfill file * fix yarn lock * upgrade to 53.0.3 * reanimated layout anim bug patch * workletize a function that wasn't getting autoworkletized anymore (#8309) * bump to expo 53.0.4 * bump RN to 0.79.2 * fix yarn lock ci * Revert "completely rearchitect toasts" This reverts commit 2e2fcaeeed527580a6c485718544b85e8b4f52b9. * final upgrades * chore: cleanup yarn lock * prettier --------- Co-authored-by: Samuel Newman --- __mocks__/multiformats/cid.js | 3 + __mocks__/multiformats/hashes/hasher.js | 7 + __tests__/lib/string.test.ts | 4 +- app.config.js | 3 +- babel.config.js | 6 +- docs/build.md | 7 +- metro.config.js | 11 +- .../expo/modules/bottomsheet/BottomSheetView.kt | 3 + .../modules/bottomsheet/DialogRootViewGroup.kt | 14 +- .../ios/ExpoBlueskyGifView.podspec | 2 +- modules/expo-emoji-picker/LICENSE | 9 + modules/expo-emoji-picker/README.md | 3 + modules/expo-emoji-picker/android/build.gradle | 46 + .../android/src/main/AndroidManifest.xml | 2 + .../modules/emojipicker/EmojiPickerModule.kt | 15 + .../modules/emojipicker/EmojiPickerModuleView.kt | 40 + modules/expo-emoji-picker/expo-module.config.json | 9 + modules/expo-emoji-picker/index.ts | 3 + .../ios/EmojiPickerModule.podspec | 25 + .../expo-emoji-picker/ios/EmojiPickerModule.swift | 11 + .../expo-emoji-picker/ios/EmojiPickerView.swift | 30 + .../expo-emoji-picker/src/EmojiPicker.android.tsx | 28 + modules/expo-emoji-picker/src/EmojiPicker.tsx | 15 + modules/expo-emoji-picker/src/EmojiPickerModule.ts | 5 + .../src/EmojiPickerModule.types.ts | 20 + modules/expo-emoji-picker/src/EmojiPickerView.tsx | 21 + .../ios/ExpoScrollForwarderView.swift | 43 +- .../src/ExpoScrollForwarder.types.ts | 2 - .../src/ExpoScrollForwarderView.ios.tsx | 3 +- .../src/ExpoScrollForwarderView.tsx | 4 +- package.json | 156 +- patches/expo-haptics+14.0.0.md | 11 - patches/expo-haptics+14.0.1.patch | 13 - patches/expo-haptics+14.1.4.patch | 13 + patches/expo-haptics+14.1.4.patch.md | 11 + patches/expo-image-manipulator+13.0.6.patch | 101 - patches/expo-media-library+17.0.6.patch | 19 - patches/expo-media-library+17.1.6.patch | 19 + patches/expo-modules-core+2.1.2.md | 3 - patches/expo-modules-core+2.2.3.patch | 15 - patches/expo-modules-core+2.3.12.patch | 15 + patches/expo-modules-core+2.3.12.patch.md | 3 + patches/expo-notifications+0.29.11.patch.md | 14 - patches/expo-notifications+0.29.14.patch | 169 -- patches/expo-notifications+0.31.1.patch | 992 +++++++ patches/expo-notifications+0.31.1.patch.md | 14 + patches/expo-updates+0.26.10.patch.md | 7 - patches/expo-updates+0.27.4.patch | 26 - patches/expo-updates+0.28.12.patch | 26 + patches/expo-updates+0.28.12.patch.md | 7 + patches/react-native+0.76.6.patch.md | 17 - patches/react-native+0.76.9.patch | 366 --- patches/react-native+0.79.2.patch | 119 + patches/react-native+0.79.2.patch.md | 13 + patches/react-native-gesture-handler+2.20.2.patch | 36 - patches/react-native-gesture-handler+2.25.0.patch | 36 + .../react-native-image-crop-picker+0.41.6.patch | 47 - .../react-native-image-crop-picker+0.42.0.patch | 48 + patches/react-native-reanimated+3.17.5.patch | 44 + patches/react-native-svg+15.11.2.patch | 57 + patches/react-native-svg+15.8.0.patch | 57 - plugins/withAppDelegateReferrer.js | 46 +- plugins/withNoBundleCompression.js | 70 - src/App.native.tsx | 1 - src/alf/atoms.ts | 4 +- src/alf/typography.tsx | 14 +- src/components/StarterPack/ProfileStarterPacks.tsx | 3 +- src/components/dms/EmojiPopup.android.tsx | 29 +- src/components/dms/EmojiPopup.tsx | 2 +- src/lib/api/index.ts | 30 +- src/lib/hooks/useDraggableScrollView.ts | 8 +- src/lib/hooks/useNotificationHandler.ts | 13 +- src/lib/strings/starter-pack.ts | 2 +- src/platform/polyfills.ts | 5 +- src/platform/polyfills.web.ts | 5 +- src/screens/Profile/Sections/Feed.tsx | 4 +- src/screens/Profile/Sections/Labels.tsx | 4 +- src/view/com/composer/Composer.tsx | 74 +- src/view/com/composer/ComposerReplyTo.tsx | 4 +- src/view/com/feeds/ProfileFeedgens.tsx | 4 +- src/view/com/lightbox/ImageViewing/index.tsx | 18 +- src/view/com/lists/ProfileLists.tsx | 4 +- src/view/com/pager/DraggableScrollView.tsx | 23 +- src/view/com/pager/TabBar.tsx | 29 +- src/view/com/pager/TabBar.web.tsx | 8 +- src/view/com/posts/ViewFullThread.tsx | 7 +- src/view/com/util/Toast.tsx | 2 +- src/view/com/util/Toast.web.tsx | 7 +- src/view/com/util/forms/NativeDropdown.web.tsx | 3 +- src/view/com/util/load-latest/LoadLatestBtn.tsx | 5 +- src/view/shell/bottom-bar/BottomBarStyles.tsx | 5 +- src/view/shell/desktop/LeftNav.tsx | 6 +- src/view/shell/index.web.tsx | 6 +- tsconfig.json | 9 +- yarn.lock | 3042 ++++++++------------ 95 files changed, 3245 insertions(+), 3119 deletions(-) create mode 100644 __mocks__/multiformats/cid.js create mode 100644 __mocks__/multiformats/hashes/hasher.js create mode 100644 modules/expo-emoji-picker/LICENSE create mode 100644 modules/expo-emoji-picker/README.md create mode 100644 modules/expo-emoji-picker/android/build.gradle create mode 100644 modules/expo-emoji-picker/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModule.kt create mode 100644 modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModuleView.kt create mode 100644 modules/expo-emoji-picker/expo-module.config.json create mode 100644 modules/expo-emoji-picker/index.ts create mode 100644 modules/expo-emoji-picker/ios/EmojiPickerModule.podspec create mode 100644 modules/expo-emoji-picker/ios/EmojiPickerModule.swift create mode 100644 modules/expo-emoji-picker/ios/EmojiPickerView.swift create mode 100644 modules/expo-emoji-picker/src/EmojiPicker.android.tsx create mode 100644 modules/expo-emoji-picker/src/EmojiPicker.tsx create mode 100644 modules/expo-emoji-picker/src/EmojiPickerModule.ts create mode 100644 modules/expo-emoji-picker/src/EmojiPickerModule.types.ts create mode 100644 modules/expo-emoji-picker/src/EmojiPickerView.tsx delete mode 100644 patches/expo-haptics+14.0.0.md delete mode 100644 patches/expo-haptics+14.0.1.patch create mode 100644 patches/expo-haptics+14.1.4.patch create mode 100644 patches/expo-haptics+14.1.4.patch.md delete mode 100644 patches/expo-image-manipulator+13.0.6.patch delete mode 100644 patches/expo-media-library+17.0.6.patch create mode 100644 patches/expo-media-library+17.1.6.patch delete mode 100644 patches/expo-modules-core+2.1.2.md delete mode 100644 patches/expo-modules-core+2.2.3.patch create mode 100644 patches/expo-modules-core+2.3.12.patch create mode 100644 patches/expo-modules-core+2.3.12.patch.md delete mode 100644 patches/expo-notifications+0.29.11.patch.md delete mode 100644 patches/expo-notifications+0.29.14.patch create mode 100644 patches/expo-notifications+0.31.1.patch create mode 100644 patches/expo-notifications+0.31.1.patch.md delete mode 100644 patches/expo-updates+0.26.10.patch.md delete mode 100644 patches/expo-updates+0.27.4.patch create mode 100644 patches/expo-updates+0.28.12.patch create mode 100644 patches/expo-updates+0.28.12.patch.md delete mode 100644 patches/react-native+0.76.6.patch.md delete mode 100644 patches/react-native+0.76.9.patch create mode 100644 patches/react-native+0.79.2.patch create mode 100644 patches/react-native+0.79.2.patch.md delete mode 100644 patches/react-native-gesture-handler+2.20.2.patch create mode 100644 patches/react-native-gesture-handler+2.25.0.patch delete mode 100644 patches/react-native-image-crop-picker+0.41.6.patch create mode 100644 patches/react-native-image-crop-picker+0.42.0.patch create mode 100644 patches/react-native-reanimated+3.17.5.patch create mode 100644 patches/react-native-svg+15.11.2.patch delete mode 100644 patches/react-native-svg+15.8.0.patch delete mode 100644 plugins/withNoBundleCompression.js diff --git a/__mocks__/multiformats/cid.js b/__mocks__/multiformats/cid.js new file mode 100644 index 000000000..788505c18 --- /dev/null +++ b/__mocks__/multiformats/cid.js @@ -0,0 +1,3 @@ +export const CID = jest.fn().mockImplementation(() => { + return {} +}) diff --git a/__mocks__/multiformats/hashes/hasher.js b/__mocks__/multiformats/hashes/hasher.js new file mode 100644 index 000000000..a525f1310 --- /dev/null +++ b/__mocks__/multiformats/hashes/hasher.js @@ -0,0 +1,7 @@ +export const from = jest.fn().mockImplementation(() => { + return { + digest: jest.fn().mockImplementation(() => { + return Promise.resolve('') + }), + } +}) diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index e212ef5c9..b018da236 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -1,11 +1,11 @@ import {RichText} from '@atproto/api' -import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' +import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' import { createStarterPackGooglePlayUri, createStarterPackLinkFromAndroidReferrer, parseStarterPackUri, -} from 'lib/strings/starter-pack' +} from '#/lib/strings/starter-pack' import {cleanError} from '../../src/lib/strings/errors' import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' import {enforceLen} from '../../src/lib/strings/helpers' diff --git a/app.config.js b/app.config.js index 0e3031329..dae8ca38c 100644 --- a/app.config.js +++ b/app.config.js @@ -193,6 +193,7 @@ module.exports = function (_config) { plugins: [ 'expo-video', 'expo-localization', + 'expo-web-browser', [ 'react-native-edge-to-edge', {android: {enforceNavigationBarContrast: false}}, @@ -242,10 +243,8 @@ module.exports = function (_config) { './plugins/withAndroidStylesAccentColorPlugin.js', './plugins/withAndroidDayNightThemePlugin.js', './plugins/withAndroidNoJitpackPlugin.js', - './plugins/withNoBundleCompression.js', './plugins/shareExtension/withShareExtensions.js', './plugins/notificationsExtension/withNotificationsExtension.js', - './plugins/withAppDelegateReferrer.js', [ 'expo-font', { diff --git a/babel.config.js b/babel.config.js index ac872648f..43e7eb54f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -17,7 +17,7 @@ module.exports = function (api) { ], plugins: [ 'macros', - ['babel-plugin-react-compiler', {target: '18'}], + ['babel-plugin-react-compiler', {target: '19'}], [ 'module:react-native-dotenv', { @@ -37,10 +37,6 @@ module.exports = function (api) { alias: { // This needs to be mirrored in tsconfig.json '#': './src', - lib: './src/lib', - platform: './src/platform', - state: './src/state', - view: './src/view', crypto: './src/platform/crypto.ts', }, }, diff --git a/docs/build.md b/docs/build.md index 7a29ab355..7817dd095 100644 --- a/docs/build.md +++ b/docs/build.md @@ -15,7 +15,7 @@ This is NOT required when developing for web. - Set up your environment [using the expo instructions](https://docs.expo.dev/guides/local-app-development/). - make sure that the JAVA_HOME points to the zulu-17 directory in your `.zshrc` or `.bashrc` file: `export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home`. DO NOT use another JDK or you will encounter build errors. -- If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods:- +- If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods:- - If you are using Apple Silicon and this is the first time you are building for RN 0.74+, you may need to run: - `arch -arm64 brew install llvm` - `sudo gem install ffi` @@ -33,7 +33,7 @@ This is NOT required when developing for web. - After initial setup: - Copy `google-services.json.example` to `google-services.json` or provide your own `google-services.json`. (A real firebase project is NOT required) - `npx expo prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change - + ### Running the Native App - iOS: `yarn ios` @@ -162,6 +162,9 @@ See [testing.md](./testing.md). `./platform/polyfills.*.ts` adds polyfills to the environment. Currently, this includes: - TextEncoder / TextDecoder +- react-native-url-polyfill +- Array#findLast (on web) +- atob (on native) ### Sentry sourcemaps diff --git a/metro.config.js b/metro.config.js index b18081336..dcdead3f5 100644 --- a/metro.config.js +++ b/metro.config.js @@ -16,22 +16,21 @@ if (process.env.BSKY_PROFILE) { cfg.resolver.assetExts = [...cfg.resolver.assetExts, 'woff2'] +// Enabled by default in RN 0.79+, but this breaks Lingui + others +cfg.resolver.unstable_enablePackageExports = false + cfg.resolver.resolveRequest = (context, moduleName, platform) => { // HACK: manually resolve a few packages that use `exports` in `package.json`. // A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing. if (moduleName.startsWith('multiformats/hashes/hasher')) { return context.resolveRequest( context, - 'multiformats/dist/src/hashes/hasher', + 'multiformats/cjs/src/hashes/hasher', platform, ) } if (moduleName.startsWith('multiformats/cid')) { - return context.resolveRequest( - context, - 'multiformats/dist/src/cid', - platform, - ) + return context.resolveRequest(context, 'multiformats/cjs/src/cid', platform) } if (moduleName === '@ipld/dag-cbor') { return context.resolveRequest(context, '@ipld/dag-cbor/src', platform) diff --git a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt index 5df163b64..31eb739a6 100644 --- a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt +++ b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt @@ -317,6 +317,9 @@ class BottomSheetView( // View overrides to pass to DialogRootViewGroup instead override fun dispatchProvideStructure(structure: ViewStructure?) { + if (structure == null) { + return + } dialogRootViewGroup.dispatchProvideStructure(structure) } diff --git a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/DialogRootViewGroup.kt b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/DialogRootViewGroup.kt index c022924e9..4b02bae9f 100644 --- a/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/DialogRootViewGroup.kt +++ b/modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/DialogRootViewGroup.kt @@ -139,19 +139,7 @@ class DialogRootViewGroup( return super.onHoverEvent(event) } - @Deprecated("Deprecated in Java") - override fun onChildStartedNativeGesture(ev: MotionEvent?) { - eventDispatcher?.let { - if (ev != null) { - jSTouchDispatcher.onChildStartedNativeGesture(ev, it) - } - } - } - - override fun onChildStartedNativeGesture( - childView: View, - ev: MotionEvent, - ) { + override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) { eventDispatcher?.let { jSTouchDispatcher.onChildStartedNativeGesture(ev, it) } jSPointerDispatcher?.onChildStartedNativeGesture(childView, ev, eventDispatcher) } diff --git a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec index 352812640..7640a5fc7 100644 --- a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec +++ b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.static_framework = true s.dependency 'ExpoModulesCore' - s.dependency 'SDWebImage', '~> 5.19.1' + s.dependency 'SDWebImage', '~> 5.21.0' s.dependency 'SDWebImageWebPCoder', '~> 0.14.6' # Swift/Objective-C compatibility diff --git a/modules/expo-emoji-picker/LICENSE b/modules/expo-emoji-picker/LICENSE new file mode 100644 index 000000000..aa0254ccb --- /dev/null +++ b/modules/expo-emoji-picker/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2025 Alan Hughes + +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/modules/expo-emoji-picker/README.md b/modules/expo-emoji-picker/README.md new file mode 100644 index 000000000..d2fb3ae05 --- /dev/null +++ b/modules/expo-emoji-picker/README.md @@ -0,0 +1,3 @@ +# expo-emoji-picker + +Based on [react-native-emoji-popup](https://github.com/okwasniewski/react-native-emoji-popup) and [expo-emoji-picker](https://github.com/alanjhughes/expo-emoji-picker) diff --git a/modules/expo-emoji-picker/android/build.gradle b/modules/expo-emoji-picker/android/build.gradle new file mode 100644 index 000000000..d34288dc7 --- /dev/null +++ b/modules/expo-emoji-picker/android/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.library' + +group = 'expo.community.modules.emojipicker' +version = '0.1.0' + +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useCoreDependencies() +useExpoPublishing() + +// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. +// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. +// Most of the time, you may like to manage the Android SDK versions yourself. +def useManagedAndroidSdkVersions = false +if (useManagedAndroidSdkVersions) { + useDefaultAndroidSdkVersions() +} else { + buildscript { + // 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 + } + } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 34) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + } + } +} + +android { + namespace "expo.community.modules.emojipicker" + defaultConfig { + versionCode 1 + versionName "0.1.0" + } + lintOptions { + abortOnError false + } + dependencies { + implementation "androidx.emoji2:emoji2-emojipicker:1.5.0" + } +} diff --git a/modules/expo-emoji-picker/android/src/main/AndroidManifest.xml b/modules/expo-emoji-picker/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..bdae66c8f --- /dev/null +++ b/modules/expo-emoji-picker/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModule.kt b/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModule.kt new file mode 100644 index 000000000..ebdc7a35e --- /dev/null +++ b/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModule.kt @@ -0,0 +1,15 @@ +package expo.community.modules.emojipicker + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import java.net.URL + +class EmojiPickerModule : Module() { + override fun definition() = ModuleDefinition { + Name("EmojiPicker") + + View(EmojiPickerModuleView::class) { + Events("onEmojiSelected") + } + } +} diff --git a/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModuleView.kt b/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModuleView.kt new file mode 100644 index 000000000..a6e5a066f --- /dev/null +++ b/modules/expo-emoji-picker/android/src/main/java/expo/community/modules/emojipicker/EmojiPickerModuleView.kt @@ -0,0 +1,40 @@ +package expo.community.modules.emojipicker + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration +import androidx.emoji2.emojipicker.EmojiPickerView +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView + + +@SuppressLint("ViewConstructor") +class EmojiPickerModuleView(context: Context, appContext: AppContext) : + ExpoView(context, appContext) { + private var emojiView: EmojiPickerView = EmojiPickerView(context) + private val onEmojiSelected by EventDispatcher() + + init { + setupView() + } + + private fun setupView() { + addView( + emojiView, LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + ) + + emojiView.setOnEmojiPickedListener { emoji -> + onEmojiSelected(mapOf("emoji" to emoji.emoji)) + } + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + removeView(emojiView) + setupView() + } +} diff --git a/modules/expo-emoji-picker/expo-module.config.json b/modules/expo-emoji-picker/expo-module.config.json new file mode 100644 index 000000000..7576240d1 --- /dev/null +++ b/modules/expo-emoji-picker/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["apple", "android"], + "apple": { + "modules": ["EmojiPickerModule"] + }, + "android": { + "modules": ["expo.community.modules.emojipicker.EmojiPickerModule"] + } +} diff --git a/modules/expo-emoji-picker/index.ts b/modules/expo-emoji-picker/index.ts new file mode 100644 index 000000000..a555b730a --- /dev/null +++ b/modules/expo-emoji-picker/index.ts @@ -0,0 +1,3 @@ +export {default as EmojiPicker} from './src/EmojiPicker' +export {default} from './src/EmojiPickerModule' +export * from './src/EmojiPickerModule.types' diff --git a/modules/expo-emoji-picker/ios/EmojiPickerModule.podspec b/modules/expo-emoji-picker/ios/EmojiPickerModule.podspec new file mode 100644 index 000000000..e6a982a70 --- /dev/null +++ b/modules/expo-emoji-picker/ios/EmojiPickerModule.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = 'EmojiPickerModule' + s.version = '1.0.0' + s.summary = 'An emoji picker for use in Bluesky' + s.description = 'An emoji picker for use in Bluesky' + s.author = 'alanjhughes' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { + :ios => '15.1', + :tvos => '15.1' + } + s.source = { git: '' } + s.swift_version = '5.4' + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'MCEmojiPicker' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-emoji-picker/ios/EmojiPickerModule.swift b/modules/expo-emoji-picker/ios/EmojiPickerModule.swift new file mode 100644 index 000000000..57b6f959a --- /dev/null +++ b/modules/expo-emoji-picker/ios/EmojiPickerModule.swift @@ -0,0 +1,11 @@ +import ExpoModulesCore + +public class EmojiPickerModule: Module { + public func definition() -> ModuleDefinition { + Name("EmojiPicker") + + View(EmojiPickerView.self) { + Events("onEmojiSelected") + } + } +} diff --git a/modules/expo-emoji-picker/ios/EmojiPickerView.swift b/modules/expo-emoji-picker/ios/EmojiPickerView.swift new file mode 100644 index 000000000..65089a2af --- /dev/null +++ b/modules/expo-emoji-picker/ios/EmojiPickerView.swift @@ -0,0 +1,30 @@ +import ExpoModulesCore +import WebKit +import MCEmojiPicker + +class EmojiPickerView: ExpoView, MCEmojiPickerDelegate { + let onEmojiSelected = EventDispatcher() + + override func layoutSubviews() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) + self.addGestureRecognizer(tapGesture) + } + + @objc func handleTap(_ gesture: UITapGestureRecognizer) { + presentEmojiPicker() + } + + func presentEmojiPicker() { + let emojiPicker = MCEmojiPickerViewController() + let reactRootVC = reactViewController() + emojiPicker.sourceView = self + emojiPicker.delegate = self + reactRootVC?.present(emojiPicker, animated: true) + } + + func didGetEmoji(emoji: String) { + onEmojiSelected([ + "emoji": emoji + ]) + } +} diff --git a/modules/expo-emoji-picker/src/EmojiPicker.android.tsx b/modules/expo-emoji-picker/src/EmojiPicker.android.tsx new file mode 100644 index 000000000..d8493b72e --- /dev/null +++ b/modules/expo-emoji-picker/src/EmojiPicker.android.tsx @@ -0,0 +1,28 @@ +import {useMemo} from 'react' +import {useColorScheme} from 'react-native' + +import {type EmojiPickerViewProps} from './EmojiPickerModule.types' +import EmojiPickerNativeView from './EmojiPickerView' + +const EmojiPicker = ({onEmojiSelected}: EmojiPickerViewProps) => { + const scheme = useColorScheme() + const styles = useMemo( + () => + ({ + flex: 1, + width: '100%', + backgroundColor: scheme === 'dark' ? '#000' : '#fff', + } as const), + [scheme], + ) + + return ( + { + onEmojiSelected(emoji) + }} + style={styles} + /> + ) +} +export default EmojiPicker diff --git a/modules/expo-emoji-picker/src/EmojiPicker.tsx b/modules/expo-emoji-picker/src/EmojiPicker.tsx new file mode 100644 index 000000000..8fee11df7 --- /dev/null +++ b/modules/expo-emoji-picker/src/EmojiPicker.tsx @@ -0,0 +1,15 @@ +import {type EmojiPickerViewProps} from './EmojiPickerModule.types' +import EmojiPickerNativeView from './EmojiPickerView' + +const EmojiPicker = ({children, onEmojiSelected}: EmojiPickerViewProps) => { + return ( + { + onEmojiSelected(emoji) + }}> + {children} + + ) +} + +export default EmojiPicker diff --git a/modules/expo-emoji-picker/src/EmojiPickerModule.ts b/modules/expo-emoji-picker/src/EmojiPickerModule.ts new file mode 100644 index 000000000..a5b04e662 --- /dev/null +++ b/modules/expo-emoji-picker/src/EmojiPickerModule.ts @@ -0,0 +1,5 @@ +import {NativeModule, requireNativeModule} from 'expo' + +declare class EmojiPickerModule extends NativeModule {} + +export default requireNativeModule('EmojiPicker') diff --git a/modules/expo-emoji-picker/src/EmojiPickerModule.types.ts b/modules/expo-emoji-picker/src/EmojiPickerModule.types.ts new file mode 100644 index 000000000..14690a38a --- /dev/null +++ b/modules/expo-emoji-picker/src/EmojiPickerModule.types.ts @@ -0,0 +1,20 @@ +import {type ViewProps} from 'react-native' + +export type EmojiSelectionListener = (event: { + nativeEvent: SelectionEvent +}) => void + +export type SelectionEvent = { + emoji: string +} + +export type EmojiPickerViewProps = ViewProps & { + /* + * Callback that will be called when an emoji is selected. + */ + onEmojiSelected: (emoji: string) => void +} + +export type EmojiPickerNativeViewProps = ViewProps & { + onEmojiSelected: EmojiSelectionListener +} diff --git a/modules/expo-emoji-picker/src/EmojiPickerView.tsx b/modules/expo-emoji-picker/src/EmojiPickerView.tsx new file mode 100644 index 000000000..0dff25fb5 --- /dev/null +++ b/modules/expo-emoji-picker/src/EmojiPickerView.tsx @@ -0,0 +1,21 @@ +import {requireNativeView} from 'expo' +import type * as React from 'react' + +import { + type EmojiPickerNativeViewProps, + type EmojiPickerViewProps, +} from './EmojiPickerModule.types' + +const NativeView: React.ComponentType = + requireNativeView('EmojiPicker') + +export default function EmojiPicker(props: EmojiPickerViewProps) { + return ( + { + props.onEmojiSelected(nativeEvent.emoji) + }} + /> + ) +} diff --git a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift index 15993ef29..e6ebef1d3 100644 --- a/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift +++ b/modules/expo-scroll-forwarder/ios/ExpoScrollForwarderView.swift @@ -9,7 +9,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { } } - private var rctScrollView: RCTScrollView? + private var scrollView: UIScrollView? private var rctRefreshCtrl: RCTRefreshControl? private var cancelGestureRecognizers: [UIGestureRecognizer]? private var animTimer: Timer? @@ -68,7 +68,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { } @IBAction func callOnPan(_ sender: UIPanGestureRecognizer) { - guard let rctsv = self.rctScrollView, let sv = rctsv.scrollView else { + guard let sv = self.scrollView else { return } @@ -113,7 +113,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { } func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) { - guard let sv = self.rctScrollView?.scrollView else { + guard let sv = self.scrollView else { return } @@ -160,32 +160,49 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { return offset } - - func tryFindScrollView() { - guard let scrollViewTag = scrollViewTag else { - return + + private func findScrollView(in view: UIView, foundCount: Int) -> UIScrollView? { + var foundCount = foundCount + if let sv = view as? UIScrollView { return sv } + for child in view.subviews { + if let found = findScrollView(in: child, foundCount: foundCount) { + if foundCount == 1 { + print("found sv: \(found)") +// return found + } else { + print("found sv: \(found)") + foundCount += 1 + } + } } + return nil + } + func tryFindScrollView() { // Before we switch to a different scrollview, we always want to remove the cancel gesture recognizer. // Otherwise we might end up with duplicates when we switch back to that scrollview. self.removeCancelGestureRecognizers() + + guard let sv = self.findScrollView(in: self.superview!.superview!.superview!, foundCount: 0) else { + print("⚠️ ExpoScrollForwarder: couldn’t find UIScrollView under tag \(tag)") + return + } - self.rctScrollView = self.appContext? - .findView(withTag: scrollViewTag, ofType: RCTScrollView.self) - self.rctRefreshCtrl = self.rctScrollView?.scrollView.refreshControl as? RCTRefreshControl + self.scrollView = sv + self.rctRefreshCtrl = sv.refreshControl as? RCTRefreshControl self.addCancelGestureRecognizers() } func addCancelGestureRecognizers() { self.cancelGestureRecognizers?.forEach { r in - self.rctScrollView?.scrollView?.addGestureRecognizer(r) + self.scrollView?.addGestureRecognizer(r) } } func removeCancelGestureRecognizers() { self.cancelGestureRecognizers?.forEach { r in - self.rctScrollView?.scrollView?.removeGestureRecognizer(r) + self.scrollView?.removeGestureRecognizer(r) } } @@ -202,7 +219,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate { } func scrollToOffset(_ offset: Int, animated: Bool = true) { - self.rctScrollView?.scroll(toOffset: CGPoint(x: 0, y: offset), animated: animated) + self.scrollView?.scrollRectToVisible(CGRect(x: 0, y: offset, width: 0, height: 0), animated: animated) } func stopTimer() { diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts b/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts index 26b9e7553..3f1e4a63d 100644 --- a/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarder.types.ts @@ -1,5 +1,3 @@ -import React from 'react' - export interface ExpoScrollForwarderViewProps { scrollViewTag: number | null children: React.ReactNode diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx index 21a2b9fb2..18bdb25c8 100644 --- a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.ios.tsx @@ -1,7 +1,6 @@ -import * as React from 'react' import {requireNativeViewManager} from 'expo-modules-core' -import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' +import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' const NativeView: React.ComponentType = requireNativeViewManager('ExpoScrollForwarder') diff --git a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx index 0f5d01c13..d66eef7cb 100644 --- a/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx +++ b/modules/expo-scroll-forwarder/src/ExpoScrollForwarderView.tsx @@ -1,6 +1,4 @@ -import React from 'react' - -import {ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' +import {type ExpoScrollForwarderViewProps} from './ExpoScrollForwarder.types' export function ExpoScrollForwarderView({ children, diff --git a/package.json b/package.json index 2ed14061f..0d96e688c 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,17 @@ "node": ">=20" }, "packageManager": "yarn@1.22.22", + "expo": { + "autolinking": { + "android": { + "buildFromSource": [ + "expo-notifications", + "expo-haptics", + "expo-media-library" + ] + } + } + }, "scripts": { "prepare": "is-ci || husky install", "postinstall": "patch-package && yarn intl:compile", @@ -58,13 +69,13 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.15.3", + "@atproto/api": "^0.15.5", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet", "@emoji-mart/react": "^1.1.1", - "@expo/html-elements": "^0.4.2", - "@expo/webpack-config": "^19.0.0", + "@expo/html-elements": "^0.12.4", + "@expo/webpack-config": "^19.0.1", "@floating-ui/dom": "^1.6.3", "@floating-ui/react-dom": "^2.0.8", "@formatjs/intl-locale": "^4.2.8", @@ -80,9 +91,9 @@ "@mattermost/react-native-paste-input": "^0.7.1", "@miblanchard/react-native-slider": "^2.3.1", "@mozzius/expo-dynamic-app-icon": "^1.5.0", - "@react-native-async-storage/async-storage": "1.23.1", - "@react-native-menu/menu": "^1.1.7", - "@react-native-picker/picker": "2.10.3", + "@react-native-async-storage/async-storage": "2.1.2", + "@react-native-menu/menu": "^1.2.3", + "@react-native-picker/picker": "2.11.0", "@react-navigation/bottom-tabs": "^6.5.20", "@react-navigation/drawer": "^6.6.15", "@react-navigation/native": "^6.1.17", @@ -119,33 +130,33 @@ "emoji-mart": "^5.5.2", "emoji-regex": "^10.4.0", "eventemitter3": "^5.0.1", - "expo": "~52.0.42", - "expo-application": "~6.0.2", - "expo-blur": "^14.0.3", - "expo-build-properties": "~0.13.2", - "expo-camera": "~16.0.18", - "expo-clipboard": "~7.0.1", - "expo-dev-client": "~5.0.19", - "expo-device": "~7.0.3", - "expo-file-system": "~18.0.12", - "expo-font": "~13.0.4", - "expo-haptics": "~14.0.1", - "expo-image": "~2.0.7", - "expo-image-manipulator": "~13.0.6", - "expo-image-picker": "~16.0.6", - "expo-linear-gradient": "~14.0.2", - "expo-linking": "~7.0.5", - "expo-localization": "~16.0.1", - "expo-media-library": "~17.0.6", - "expo-notifications": "~0.29.14", - "expo-screen-orientation": "~8.0.4", - "expo-sharing": "~13.0.1", - "expo-splash-screen": "~0.29.22", - "expo-system-ui": "~4.0.9", - "expo-task-manager": "~12.0.6", - "expo-updates": "~0.27.4", - "expo-video": "~2.0.6", - "expo-web-browser": "~14.0.2", + "expo": "^53.0.5", + "expo-application": "~6.1.4", + "expo-blur": "~14.1.4", + "expo-build-properties": "~0.14.6", + "expo-camera": "~16.1.6", + "expo-clipboard": "~7.1.4", + "expo-dev-client": "~5.1.7", + "expo-device": "~7.1.4", + "expo-file-system": "~18.1.8", + "expo-font": "~13.3.0", + "expo-haptics": "~14.1.4", + "expo-image": "~2.1.6", + "expo-image-manipulator": "~13.1.5", + "expo-image-picker": "~16.1.4", + "expo-linear-gradient": "~14.1.4", + "expo-linking": "~7.1.4", + "expo-localization": "~16.1.5", + "expo-media-library": "~17.1.6", + "expo-notifications": "~0.31.1", + "expo-screen-orientation": "~8.1.5", + "expo-sharing": "~13.1.5", + "expo-splash-screen": "~0.30.8", + "expo-system-ui": "~5.0.7", + "expo-task-manager": "~13.1.5", + "expo-updates": "~0.28.12", + "expo-video": "~2.1.8", + "expo-web-browser": "~14.1.6", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", "hls.js": "^1.5.11", @@ -157,47 +168,47 @@ "lodash.isequal": "^4.5.0", "lodash.shuffle": "^4.2.0", "lodash.throttle": "^4.1.1", - "multiformats": "^13.1.0", + "multiformats": "9.9.0", "nanoid": "^5.0.5", "normalize-url": "^8.0.0", "patch-package": "^6.5.1", "postinstall-postinstall": "^2.1.0", "psl": "^1.9.0", "radix-ui": "^1.2.0", - "react": "18.3.1", - "react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110", - "react-dom": "18.3.1", + "react": "19.0.0", + "react-compiler-runtime": "^19.1.0-rc.1", + "react-dom": "19.0.0", "react-image-crop": "^11.0.7", - "react-keyed-flatten-children": "^3.0.0", - "react-native": "0.76.9", - "react-native-compressor": "1.10.3", - "react-native-date-picker": "^5.0.7", - "react-native-drawer-layout": "^4.1.1", + "react-is": "19", + "react-keyed-flatten-children": "^5.0.0", + "react-native": "0.79.2", + "react-native-compressor": "1.11.0", + "react-native-date-picker": "^5.0.12", + "react-native-drawer-layout": "^4.1.6", "react-native-edge-to-edge": "^1.6.0", - "react-native-emoji-popup": "^0.1.2", - "react-native-gesture-handler": "2.20.2", + "react-native-gesture-handler": "2.25.0", "react-native-get-random-values": "~1.11.0", - "react-native-image-crop-picker": "^0.41.6", + "react-native-image-crop-picker": "^0.42.0", "react-native-ios-context-menu": "^1.15.3", "react-native-keyboard-controller": "^1.17.1", "react-native-mmkv": "^2.12.2", - "react-native-pager-view": "6.5.1", + "react-native-pager-view": "6.7.1", "react-native-picker-select": "^9.3.1", "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", - "react-native-reanimated": "~3.16.1", + "react-native-reanimated": "~3.17.5", "react-native-root-siblings": "^4.1.1", - "react-native-safe-area-context": "4.12.0", - "react-native-screens": "~4.4.0", - "react-native-svg": "15.8.0", + "react-native-safe-area-context": "5.4.0", + "react-native-screens": "~4.10.0", + "react-native-svg": "15.11.2", "react-native-uitextview": "^1.4.0", "react-native-url-polyfill": "^1.3.0", - "react-native-uuid": "^2.0.2", + "react-native-uuid": "^2.0.3", "react-native-view-shot": "^4.0.3", - "react-native-web": "~0.19.13", + "react-native-web": "~0.20.0", "react-native-web-webview": "^1.0.2", - "react-native-webview": "13.12.5", - "react-remove-scroll-bar": "^2.3.6", + "react-native-webview": "13.13.5", + "react-remove-scroll-bar": "^2.3.8", "react-responsive": "^9.0.2", "react-textarea-autosize": "^8.5.3", "rn-fetch-blob": "^0.12.0", @@ -209,77 +220,74 @@ "zod": "^3.20.2" }, "devDependencies": { - "@atproto/dev-env": "^0.3.87", + "@atproto/dev-env": "^0.3.128", "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/runtime": "^7.26.0", - "@did-plc/server": "^0.0.1", - "@expo/config-plugins": "9.0.10", + "@expo/config-plugins": "~10.0.2", "@lingui/cli": "^4.14.1", "@lingui/macro": "^4.14.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", - "@react-native/eslint-config": "^0.76.9", - "@react-native/typescript-config": "^0.76.9", + "@react-native/eslint-config": "^0.79.2", + "@react-native/typescript-config": "^0.79.2", "@sentry/webpack-plugin": "^3.2.2", "@testing-library/jest-native": "^5.4.3", - "@testing-library/react-native": "^12.8.1", + "@testing-library/react-native": "^13.2.0", "@types/jest": "^29.4.0", "@types/lodash.chunk": "^4.2.7", "@types/lodash.debounce": "^4.0.7", "@types/lodash.isequal": "^4.5.6", "@types/lodash.shuffle": "^4.2.7", "@types/psl": "^1.1.1", - "@types/react-dom": "^18.3.1", + "@types/react-dom": "^19.1.2", "@types/react-responsive": "^8.0.5", - "@types/react-test-renderer": "^17.0.1", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "babel-jest": "^29.7.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-module-resolver": "^5.0.2", - "babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110", - "babel-preset-expo": "^12.0.2", + "babel-plugin-react-compiler": "^19.1.0-rc.1", + "babel-preset-expo": "~13.1.11", "eslint": "^8.19.0", "eslint-plugin-bsky-internal": "link:./eslint", "eslint-plugin-ft-flow": "^2.0.3", "eslint-plugin-import": "^2.31.0", "eslint-plugin-lingui": "^0.2.0", "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110", + "eslint-plugin-react-compiler": "^19.1.0-rc.1", "eslint-plugin-react-native-a11y": "^3.3.0", "eslint-plugin-simple-import-sort": "^12.0.0", "file-loader": "6.2.0", "husky": "^8.0.3", "is-ci": "^3.0.1", "jest": "^29.7.0", - "jest-expo": "~52.0.6", + "jest-expo": "~53.0.3", "jest-junit": "^16.0.0", "lint-staged": "^13.2.3", "lockfile-lint": "^4.14.0", - "metro-react-native-babel-preset": "^0.76.9", + "metro-react-native-babel-preset": "^0.77.0", "prettier": "^2.8.3", "react-native-dotenv": "^3.4.11", "react-refresh": "^0.14.0", - "react-test-renderer": "18.2.0", "svgo": "^3.3.2", "ts-node": "^10.9.1", - "typescript": "^5.7.2", + "typescript": "~5.8.3", "webpack-bundle-analyzer": "^4.10.1" }, "resolutions": { "@expo/image-utils": "0.6.3", - "@react-native/babel-preset": "0.76.9", - "@react-native/normalize-colors": "0.76.1", + "@react-native/babel-preset": "0.79.2", + "@react-native/normalize-colors": "0.79.2", "@types/react": "^18", "**/expo-constants": "17.0.3", "**/expo-device": "7.0.1", - "**/zod": "3.23.8" + "**/zod": "3.23.8", + "**/multiformats": "9.9.0" }, "jest": { "preset": "jest-expo/ios", "setupFilesAfterEnv": [ - "./jest/jestSetup.js", - "@testing-library/jest-native/extend-expect" + "./jest/jestSetup.js" ], "moduleFileExtensions": [ "ts", diff --git a/patches/expo-haptics+14.0.0.md b/patches/expo-haptics+14.0.0.md deleted file mode 100644 index afa7395bc..000000000 --- a/patches/expo-haptics+14.0.0.md +++ /dev/null @@ -1,11 +0,0 @@ -# Expo Haptics Patch - -Whenever we migrated to Expo Haptics, there was a difference between how the previous and new libraries handled the -Android implementation of an iOS "light" haptic. The previous library used the `Vibration` API solely, which does not -have any configuration for intensity of vibration. The `Vibration` API has also been deprecated since SDK 26. See: -https://github.com/mkuczera/react-native-haptic-feedback/blob/master/android/src/main/java/com/mkuczera/vibrateFactory/VibrateWithDuration.java - -Expo Haptics is using `VibrationManager` API on SDK >= 31. See: https://github.com/expo/expo/blob/main/packages/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt#L19 -The timing and intensity of their haptic configurations though differs greatly from the original implementation. This -patch uses the new `VibrationManager` API to create the same vibration that would have been seen in the deprecated -`Vibration` API. diff --git a/patches/expo-haptics+14.0.1.patch b/patches/expo-haptics+14.0.1.patch deleted file mode 100644 index 9c7b9a666..000000000 --- a/patches/expo-haptics+14.0.1.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt -index 1520465..6ea988a 100644 ---- a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt -+++ b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt -@@ -42,7 +42,7 @@ class HapticsModule : Module() { - - private fun vibrate(type: HapticsVibrationType) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { -- vibrator.vibrate(VibrationEffect.createWaveform(type.timings, type.amplitudes, -1)) -+ vibrator.vibrate(VibrationEffect.createWaveform(type.oldSDKPattern, intArrayOf(0, 100), -1)) - } else { - @Suppress("DEPRECATION") - vibrator.vibrate(type.oldSDKPattern, -1) diff --git a/patches/expo-haptics+14.1.4.patch b/patches/expo-haptics+14.1.4.patch new file mode 100644 index 000000000..9c7b9a666 --- /dev/null +++ b/patches/expo-haptics+14.1.4.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt +index 1520465..6ea988a 100644 +--- a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt ++++ b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt +@@ -42,7 +42,7 @@ class HapticsModule : Module() { + + private fun vibrate(type: HapticsVibrationType) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { +- vibrator.vibrate(VibrationEffect.createWaveform(type.timings, type.amplitudes, -1)) ++ vibrator.vibrate(VibrationEffect.createWaveform(type.oldSDKPattern, intArrayOf(0, 100), -1)) + } else { + @Suppress("DEPRECATION") + vibrator.vibrate(type.oldSDKPattern, -1) diff --git a/patches/expo-haptics+14.1.4.patch.md b/patches/expo-haptics+14.1.4.patch.md new file mode 100644 index 000000000..afa7395bc --- /dev/null +++ b/patches/expo-haptics+14.1.4.patch.md @@ -0,0 +1,11 @@ +# Expo Haptics Patch + +Whenever we migrated to Expo Haptics, there was a difference between how the previous and new libraries handled the +Android implementation of an iOS "light" haptic. The previous library used the `Vibration` API solely, which does not +have any configuration for intensity of vibration. The `Vibration` API has also been deprecated since SDK 26. See: +https://github.com/mkuczera/react-native-haptic-feedback/blob/master/android/src/main/java/com/mkuczera/vibrateFactory/VibrateWithDuration.java + +Expo Haptics is using `VibrationManager` API on SDK >= 31. See: https://github.com/expo/expo/blob/main/packages/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt#L19 +The timing and intensity of their haptic configurations though differs greatly from the original implementation. This +patch uses the new `VibrationManager` API to create the same vibration that would have been seen in the deprecated +`Vibration` API. diff --git a/patches/expo-image-manipulator+13.0.6.patch b/patches/expo-image-manipulator+13.0.6.patch deleted file mode 100644 index 02d4d5fb9..000000000 --- a/patches/expo-image-manipulator+13.0.6.patch +++ /dev/null @@ -1,101 +0,0 @@ -diff --git a/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts b/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts -new file mode 100644 -index 0000000..babbb3b ---- /dev/null -+++ b/node_modules/expo-image-manipulator/src/ImageManipulator.web.ts -@@ -0,0 +1,60 @@ -+import { useReleasingSharedObject } from 'expo-modules-core'; -+import { SharedRef } from 'expo-modules-core/types'; -+ -+import { Action, ImageResult, SaveFormat, SaveOptions } from './ImageManipulator.types'; -+import { ImageManipulatorContext } from './ImageManipulatorContext'; -+import ExpoImageManipulator from './NativeImageManipulatorModule'; -+import { validateArguments } from './validators'; -+ -+// @needsAudit -+/** -+ * Manipulate the image provided via `uri`. Available modifications are rotating, flipping (mirroring), -+ * resizing and cropping. Each invocation results in a new file. With one invocation you can provide -+ * a set of actions to perform over the image. Overwriting the source file would not have an effect -+ * in displaying the result as images are cached. -+ * @param uri URI of the file to manipulate. Should be on the local file system or a base64 data URI. -+ * @param actions An array of objects representing manipulation options. Each object should have -+ * __only one__ of the keys that corresponds to specific transformation. -+ * @param saveOptions A map defining how modified image should be saved. -+ * @return Promise which fulfils with [`ImageResult`](#imageresult) object. -+ * @deprecated It has been replaced by the new, contextual and object-oriented API. -+ * Use [`ImageManipulator.manipulate`](#manipulateuri) or [`useImageManipulator`](#useimagemanipulatoruri) instead. -+ */ -+export async function manipulateAsync( -+ uri: string, -+ actions: Action[] = [], -+ saveOptions: SaveOptions = {} -+): Promise { -+ validateArguments(uri, actions, saveOptions); -+ -+ const { format = SaveFormat.JPEG, ...rest } = saveOptions; -+ const context = ExpoImageManipulator.manipulate(uri); -+ -+ for (const action of actions) { -+ if ('resize' in action) { -+ context.resize(action.resize); -+ } else if ('rotate' in action) { -+ context.rotate(action.rotate); -+ } else if ('flip' in action) { -+ context.flip(action.flip); -+ } else if ('crop' in action) { -+ context.crop(action.crop); -+ } else if ('extent' in action && context.extent) { -+ context.extent(action.extent); -+ } -+ } -+ const image = await context.renderAsync(saveOptions.compress); -+ const result = await image.saveAsync({ format, ...rest }); -+ -+ // These shared objects will not be used anymore, so free up some memory. -+ context.release(); -+ image.release(); -+ -+ return result; -+} -+ -+export function useImageManipulator(source: string | SharedRef<'image'>): ImageManipulatorContext { -+ return useReleasingSharedObject(() => ExpoImageManipulator.manipulate(source), [source]); -+} -+ -+export { ExpoImageManipulator as ImageManipulator }; -diff --git a/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts b/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts -index 120d8d3..f8aa49c 100644 ---- a/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts -+++ b/node_modules/expo-image-manipulator/src/ImageManipulatorContext.ts -@@ -52,7 +52,7 @@ export declare class ImageManipulatorContext extends SharedObject { - /** - * Awaits for all manipulation tasks to finish and resolves with a reference to the resulted native image. - */ -- renderAsync(): Promise; -+ renderAsync(compress?: number): Promise; - } - - export default ExpoImageManipulator.Context as typeof ImageManipulatorContext; -diff --git a/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts b/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts -index 428848c..363a57a 100644 ---- a/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts -+++ b/node_modules/expo-image-manipulator/src/web/ImageManipulatorContext.web.ts -@@ -41,7 +41,7 @@ export default class ImageManipulatorContext extends SharedObject { - return this; - } - -- async renderAsync(): Promise { -+ async renderAsync(compress?: number): Promise { - const canvas = await this.currentTask; - - return new Promise((resolve) => { -@@ -49,7 +49,7 @@ export default class ImageManipulatorContext extends SharedObject { - const url = blob ? URL.createObjectURL(blob) : canvas.toDataURL(); - - resolve(new ImageManipulatorImageRef(url, canvas.width, canvas.height)); -- }); -+ }, typeof compress === 'number' ? 'image/jpeg' : undefined, compress); - }); - } - diff --git a/patches/expo-media-library+17.0.6.patch b/patches/expo-media-library+17.0.6.patch deleted file mode 100644 index 7ef57f768..000000000 --- a/patches/expo-media-library+17.0.6.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt -index 64e6efe..e61485e 100644 ---- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt -+++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt -@@ -111,12 +111,12 @@ class MediaLibraryModule : Module() { - } - - AsyncFunction("createAssetAsync") { localUri: String, promise: Promise -> -- throwUnlessPermissionsGranted { -+ //throwUnlessPermissionsGranted { - withModuleScope(promise) { - CreateAsset(context, localUri, promise) - .execute() - } -- } -+ //} - } - - AsyncFunction("addAssetsToAlbumAsync") { assetsId: List, albumId: String, copyToAlbum: Boolean, promise: Promise -> diff --git a/patches/expo-media-library+17.1.6.patch b/patches/expo-media-library+17.1.6.patch new file mode 100644 index 000000000..4fa853ec3 --- /dev/null +++ b/patches/expo-media-library+17.1.6.patch @@ -0,0 +1,19 @@ +diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt +index f1255e8..a9b49e5 100644 +--- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt ++++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt +@@ -112,11 +112,9 @@ class MediaLibraryModule : Module() { + } + + AsyncFunction("createAssetAsync") { localUri: String, albumId: String?, promise: Promise -> +- throwUnlessPermissionsGranted { +- withModuleScope(promise) { +- CreateAssetWithAlbumId(context, localUri, promise, true, albumId) +- .execute() +- } ++ withModuleScope(promise) { ++ CreateAssetWithAlbumId(context, localUri, promise, true, albumId) ++ .execute() + } + } + diff --git a/patches/expo-modules-core+2.1.2.md b/patches/expo-modules-core+2.1.2.md deleted file mode 100644 index a71324c19..000000000 --- a/patches/expo-modules-core+2.1.2.md +++ /dev/null @@ -1,3 +0,0 @@ -## expo-modules-core Patch - -This patch fixes an issue where bitdrift's API stream gets blocked by the Expo interceptor used to power the devtools diff --git a/patches/expo-modules-core+2.2.3.patch b/patches/expo-modules-core+2.2.3.patch deleted file mode 100644 index f3d9bfd14..000000000 --- a/patches/expo-modules-core+2.2.3.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -index 47c4d15..afe138d 100644 ---- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt -@@ -125,6 +125,10 @@ internal fun peekResponseBody( - } - - internal fun shouldParseBody(response: Response): Boolean { -+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") { -+ return false -+ } -+ - // Check for Content-Type - val skipContentTypes = listOf( - "text/event-stream", // Server Sent Events diff --git a/patches/expo-modules-core+2.3.12.patch b/patches/expo-modules-core+2.3.12.patch new file mode 100644 index 000000000..f3d9bfd14 --- /dev/null +++ b/patches/expo-modules-core+2.3.12.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +index 47c4d15..afe138d 100644 +--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt ++++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +@@ -125,6 +125,10 @@ internal fun peekResponseBody( + } + + internal fun shouldParseBody(response: Response): Boolean { ++ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") { ++ return false ++ } ++ + // Check for Content-Type + val skipContentTypes = listOf( + "text/event-stream", // Server Sent Events diff --git a/patches/expo-modules-core+2.3.12.patch.md b/patches/expo-modules-core+2.3.12.patch.md new file mode 100644 index 000000000..a71324c19 --- /dev/null +++ b/patches/expo-modules-core+2.3.12.patch.md @@ -0,0 +1,3 @@ +## expo-modules-core Patch + +This patch fixes an issue where bitdrift's API stream gets blocked by the Expo interceptor used to power the devtools diff --git a/patches/expo-notifications+0.29.11.patch.md b/patches/expo-notifications+0.29.11.patch.md deleted file mode 100644 index 05f841725..000000000 --- a/patches/expo-notifications+0.29.11.patch.md +++ /dev/null @@ -1,14 +0,0 @@ -## LOAD BEARING PATCH, DO NOT REMOVE - -## Expo-Notifications Patch - -This patch supports the Android background notification handling module. Incoming messages -in `onMessageReceived` are sent to the module for handling. - -It also allows us to set the Android notification channel ID from the notification `data`, rather -than the `notification` object in the payload. - -### `setBadgeCountAsync` fix on Android - -`ShortcutBadger`'s `setCount` doesn't work for clearing the badge on Android for some reason. Instead, let's use the -Android API for clearing the badge. diff --git a/patches/expo-notifications+0.29.14.patch b/patches/expo-notifications+0.29.14.patch deleted file mode 100644 index dd70cc781..000000000 --- a/patches/expo-notifications+0.29.14.patch +++ /dev/null @@ -1,169 +0,0 @@ -diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle -index 13bffbb..5ebbede 100644 ---- a/node_modules/expo-notifications/android/build.gradle -+++ b/node_modules/expo-notifications/android/build.gradle -@@ -46,6 +46,7 @@ dependencies { - implementation 'com.google.firebase:firebase-messaging:24.0.1' - - implementation 'me.leolin:ShortcutBadger:1.1.22@aar' -+ implementation project(':expo-background-notification-handler') - - if (project.findProject(':expo-modules-test-core')) { - testImplementation project(':expo-modules-test-core') -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt -index 7b99e6c..45a450d 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt -@@ -15,6 +15,7 @@ import org.json.JSONObject - * This interface exists to provide a common API for both classes. - * */ - interface INotificationContent : Parcelable { -+ val channelId: String? - val title: String? - val text: String? - val subText: String? -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java -index 191b64e..fe8b3c5 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java -@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation; - * Refactoring this class may require a migration strategy for the data stored in SharedPreferences. - */ - public class NotificationContent implements Parcelable, Serializable, INotificationContent { -+ private String mChannelId; - private String mTitle; - private String mText; - private String mSubtitle; -@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat - } - }; - -+ @Nullable -+ public String getChannelId() { -+ return mChannelId; -+ } -+ - @Nullable - public String getTitle() { - return mTitle; -@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat - } - - protected NotificationContent(Parcel in) { -+ mChannelId = in.readString(); - mTitle = in.readString(); - mText = in.readString(); - mSubtitle = in.readString(); -@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat - - @Override - public void writeToParcel(Parcel dest, int flags) { -+ dest.writeString(mChannelId); - dest.writeString(mTitle); - dest.writeString(mText); - dest.writeString(mSubtitle); -@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat - private static final long serialVersionUID = 397666843266836802L; - - private void writeObject(java.io.ObjectOutputStream out) throws IOException { -+ out.writeObject(mChannelId); - out.writeObject(mTitle); - out.writeObject(mText); - out.writeObject(mSubtitle); -@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat - useDefaultVibrationPattern(); - } - -+ public Builder setChannelId(String channelId) { -+ content.mChannelId = channelId; -+ return this; -+ } -+ - public Builder setTitle(String title) { - content.mTitle = title; - return this; -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt -index 39b5aad..e50797d 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt -@@ -11,6 +11,9 @@ import org.json.JSONObject - * */ - @JvmInline - value class NotificationData(private val data: Map) { -+ val channelId: String? -+ get() = data["channelId"] -+ - val title: String? - get() = data["title"] - -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt -index d2cc6cf..6a48ff2 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt -@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot - return remoteMessage.notification?.imageUrl != null - } - -+ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId -+ - override val title = remoteMessage.notification?.title ?: notificationData.title - - override val text = remoteMessage.notification?.body ?: notificationData.message -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt -index 8ca6ec5..57c3599 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt -@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder( - builder.setOngoing(content.isSticky) - - // see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates -+ content.channelId?.let { -+ builder.setChannelId(it) -+ } - builder.setContentTitle(content.title) - builder.setContentText(content.text) - builder.setSubText(content.subText) -diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt -index 9f22441..5f92f80 100644 ---- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt -+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt -@@ -2,6 +2,9 @@ package expo.modules.notifications.service.delegates - - import android.content.Context - import com.google.firebase.messaging.RemoteMessage -+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler -+import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface -+import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule - import expo.modules.interfaces.taskManager.TaskServiceProviderHelper - import expo.modules.notifications.notifications.RemoteMessageSerializer - import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer -@@ -17,7 +20,8 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener - import java.lang.ref.WeakReference - import java.util.* - --open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate { -+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, -+ BackgroundNotificationHandlerInterface { - companion object { - // Unfortunately we cannot save state between instances of a service other way - // than by static properties. Fortunately, using weak references we can -@@ -94,8 +98,17 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM - DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage) - val notification = createNotification(remoteMessage) - DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification) -- NotificationsService.receive(context, notification) -- runTaskManagerTasks(remoteMessage) -+ -+ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) { -+ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage) -+ } else { -+ showMessage(remoteMessage) -+ runTaskManagerTasks(remoteMessage) -+ } -+ } -+ -+ override fun showMessage(remoteMessage: RemoteMessage) { -+ NotificationsService.receive(context, createNotification(remoteMessage)) - } - - private fun runTaskManagerTasks(remoteMessage: RemoteMessage) { diff --git a/patches/expo-notifications+0.31.1.patch b/patches/expo-notifications+0.31.1.patch new file mode 100644 index 000000000..56e639a26 --- /dev/null +++ b/patches/expo-notifications+0.31.1.patch @@ -0,0 +1,992 @@ +diff --git a/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock +new file mode 100644 +index 0000000..883ef6a +Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/checksums/checksums.lock differ +diff --git a/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/dependencies-accessors/gc.properties +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin +new file mode 100644 +index 0000000..f76dd23 +Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileChanges/last-build.bin differ +diff --git a/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock +new file mode 100644 +index 0000000..774caf7 +Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/8.10/fileHashes/fileHashes.lock differ +diff --git a/node_modules/expo-notifications/android/.gradle/8.10/gc.properties b/node_modules/expo-notifications/android/.gradle/8.10/gc.properties +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +new file mode 100644 +index 0000000..a3c1514 +Binary files /dev/null and b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ +diff --git a/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties +new file mode 100644 +index 0000000..0e5b4da +--- /dev/null ++++ b/node_modules/expo-notifications/android/.gradle/buildOutputCleanup/cache.properties +@@ -0,0 +1,2 @@ ++#Thu Apr 24 20:44:36 PDT 2025 ++gradle.version=8.10 +diff --git a/node_modules/expo-notifications/android/.gradle/config.properties b/node_modules/expo-notifications/android/.gradle/config.properties +new file mode 100644 +index 0000000..0bd71c6 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.gradle/config.properties +@@ -0,0 +1,2 @@ ++#Thu Apr 24 20:44:32 PDT 2025 ++java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home +diff --git a/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties b/node_modules/expo-notifications/android/.gradle/vcs-1/gc.properties +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/expo-notifications/android/.idea/.gitignore b/node_modules/expo-notifications/android/.idea/.gitignore +new file mode 100644 +index 0000000..26d3352 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/.gitignore +@@ -0,0 +1,3 @@ ++# Default ignored files ++/shelf/ ++/workspace.xml +diff --git a/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml +new file mode 100644 +index 0000000..4a53bee +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/AndroidProjectSystem.xml +@@ -0,0 +1,6 @@ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml +new file mode 100644 +index 0000000..9e9ba09 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/caches/deviceStreaming.xml +@@ -0,0 +1,607 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/gradle.xml b/node_modules/expo-notifications/android/.idea/gradle.xml +new file mode 100644 +index 0000000..b838237 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/gradle.xml +@@ -0,0 +1,12 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/migrations.xml b/node_modules/expo-notifications/android/.idea/migrations.xml +new file mode 100644 +index 0000000..f8051a6 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/migrations.xml +@@ -0,0 +1,10 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/misc.xml b/node_modules/expo-notifications/android/.idea/misc.xml +new file mode 100644 +index 0000000..3040d03 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/misc.xml +@@ -0,0 +1,10 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/runConfigurations.xml b/node_modules/expo-notifications/android/.idea/runConfigurations.xml +new file mode 100644 +index 0000000..16660f1 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/runConfigurations.xml +@@ -0,0 +1,17 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/.idea/workspace.xml b/node_modules/expo-notifications/android/.idea/workspace.xml +new file mode 100644 +index 0000000..df26928 +--- /dev/null ++++ b/node_modules/expo-notifications/android/.idea/workspace.xml +@@ -0,0 +1,47 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 1745552672693 ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle +index bc479ee..1ebfa00 100644 +--- a/node_modules/expo-notifications/android/build.gradle ++++ b/node_modules/expo-notifications/android/build.gradle +@@ -42,6 +42,7 @@ dependencies { + implementation 'com.google.firebase:firebase-messaging:24.0.1' + + implementation 'me.leolin:ShortcutBadger:1.1.22@aar' ++ implementation project(':expo-background-notification-handler') + + if (project.findProject(':expo-modules-test-core')) { + testImplementation project(':expo-modules-test-core') +diff --git a/node_modules/expo-notifications/android/local.properties b/node_modules/expo-notifications/android/local.properties +new file mode 100644 +index 0000000..ab4c86d +--- /dev/null ++++ b/node_modules/expo-notifications/android/local.properties +@@ -0,0 +1,8 @@ ++## This file must *NOT* be checked into Version Control Systems, ++# as it contains information specific to your local configuration. ++# ++# Location of the SDK. This is only used by Gradle. ++# For customization when using a Version Control System, please read the ++# header note. ++#Thu Apr 24 20:44:32 PDT 2025 ++sdk.dir=/Users/hailey/Library/Android/sdk +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt +index 7b99e6c..45a450d 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/interfaces/INotificationContent.kt +@@ -15,6 +15,7 @@ import org.json.JSONObject + * This interface exists to provide a common API for both classes. + * */ + interface INotificationContent : Parcelable { ++ val channelId: String? + val title: String? + val text: String? + val subText: String? +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +index 191b64e..fe8b3c5 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java +@@ -35,6 +35,7 @@ import kotlin.coroutines.Continuation; + * Refactoring this class may require a migration strategy for the data stored in SharedPreferences. + */ + public class NotificationContent implements Parcelable, Serializable, INotificationContent { ++ private String mChannelId; + private String mTitle; + private String mText; + private String mSubtitle; +@@ -65,6 +66,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat + } + }; + ++ @Nullable ++ public String getChannelId() { ++ return mChannelId; ++ } ++ + @Nullable + public String getTitle() { + return mTitle; +@@ -158,6 +164,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat + } + + protected NotificationContent(Parcel in) { ++ mChannelId = in.readString(); + mTitle = in.readString(); + mText = in.readString(); + mSubtitle = in.readString(); +@@ -183,6 +190,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat + + @Override + public void writeToParcel(Parcel dest, int flags) { ++ dest.writeString(mChannelId); + dest.writeString(mTitle); + dest.writeString(mText); + dest.writeString(mSubtitle); +@@ -203,6 +211,7 @@ public class NotificationContent implements Parcelable, Serializable, INotificat + private static final long serialVersionUID = 397666843266836802L; + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { ++ out.writeObject(mChannelId); + out.writeObject(mTitle); + out.writeObject(mText); + out.writeObject(mSubtitle); +@@ -285,6 +294,11 @@ public class NotificationContent implements Parcelable, Serializable, INotificat + useDefaultVibrationPattern(); + } + ++ public Builder setChannelId(String channelId) { ++ content.mChannelId = channelId; ++ return this; ++ } ++ + public Builder setTitle(String title) { + content.mTitle = title; + return this; +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt +index 3af254c..3c77e9d 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationData.kt +@@ -11,6 +11,9 @@ import org.json.JSONObject + * */ + @JvmInline + value class NotificationData(private val data: Map) { ++ val channelId: String? ++ get() = data["channelId"] ++ + val title: String? + get() = data["title"] + +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt +index d2cc6cf..6a48ff2 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/RemoteNotificationContent.kt +@@ -31,6 +31,8 @@ class RemoteNotificationContent(private val remoteMessage: RemoteMessage) : INot + return remoteMessage.notification?.imageUrl != null + } + ++ override val channelId = remoteMessage.notification?.channelId ?: notificationData.channelId ++ + override val title = remoteMessage.notification?.title ?: notificationData.title + + override val text = remoteMessage.notification?.body ?: notificationData.message +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt +index 98f003f..2f745e8 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.kt +@@ -101,6 +101,9 @@ open class ExpoNotificationBuilder( + builder.setOngoing(content.isSticky) + + // see "Notification anatomy" https://developer.android.com/develop/ui/views/notifications#Templates ++ content.channelId?.let { ++ builder.setChannelId(it) ++ } + builder.setContentTitle(content.title) + builder.setContentText(content.text) + builder.setSubText(content.subText) +diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +index 90ca4ff..9d4cb09 100644 +--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt ++++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt +@@ -3,6 +3,9 @@ package expo.modules.notifications.service.delegates + import android.content.Context + import android.os.Bundle + import com.google.firebase.messaging.RemoteMessage ++import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandler ++import expo.modules.backgroundnotificationhandler.BackgroundNotificationHandlerInterface ++import expo.modules.backgroundnotificationhandler.ExpoBackgroundNotificationHandlerModule + import expo.modules.interfaces.taskManager.TaskServiceProviderHelper + import expo.modules.notifications.notifications.RemoteMessageSerializer + import expo.modules.notifications.notifications.background.BackgroundRemoteNotificationTaskConsumer +@@ -18,7 +21,7 @@ import expo.modules.notifications.tokens.interfaces.FirebaseTokenListener + import java.lang.ref.WeakReference + import java.util.* + +-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate { ++open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface{ + companion object { + // Unfortunately we cannot save state between instances of a service other way + // than by static properties. Fortunately, using weak references we can +@@ -105,8 +108,19 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM + DebugLogging.logRemoteMessage("FirebaseMessagingDelegate.onMessageReceived: message", remoteMessage) + val notification = createNotification(remoteMessage) + DebugLogging.logNotification("FirebaseMessagingDelegate.onMessageReceived: notification", notification) +- NotificationsService.receive(context, notification) +- runTaskManagerTasks(context.applicationContext, RemoteMessageSerializer.toBundle(remoteMessage)) ++ if (!ExpoBackgroundNotificationHandlerModule.isForegrounded) { ++ BackgroundNotificationHandler(context, this).handleMessage(remoteMessage) ++ } else { ++ NotificationsService.receive(context, notification) ++ runTaskManagerTasks( ++ context.applicationContext, ++ RemoteMessageSerializer.toBundle(remoteMessage) ++ ) ++ } ++ } ++ ++ override fun showMessage(remoteMessage: RemoteMessage) { ++ NotificationsService.receive(context, createNotification(remoteMessage)) + } + + protected fun createNotification(remoteMessage: RemoteMessage): Notification { diff --git a/patches/expo-notifications+0.31.1.patch.md b/patches/expo-notifications+0.31.1.patch.md new file mode 100644 index 000000000..05f841725 --- /dev/null +++ b/patches/expo-notifications+0.31.1.patch.md @@ -0,0 +1,14 @@ +## LOAD BEARING PATCH, DO NOT REMOVE + +## Expo-Notifications Patch + +This patch supports the Android background notification handling module. Incoming messages +in `onMessageReceived` are sent to the module for handling. + +It also allows us to set the Android notification channel ID from the notification `data`, rather +than the `notification` object in the payload. + +### `setBadgeCountAsync` fix on Android + +`ShortcutBadger`'s `setCount` doesn't work for clearing the badge on Android for some reason. Instead, let's use the +Android API for clearing the badge. diff --git a/patches/expo-updates+0.26.10.patch.md b/patches/expo-updates+0.26.10.patch.md deleted file mode 100644 index 6d5d7093d..000000000 --- a/patches/expo-updates+0.26.10.patch.md +++ /dev/null @@ -1,7 +0,0 @@ -# Expo-Updates Patch - -This is a small patch to convert timestamp formats that are returned from the backend. Instead of relying on the -backend to return the correct format for a specific format (the format required on Android is not the same as on iOS) -we can just add this conversion in. - -Don't remove unless we make changes on the backend to support both platforms. diff --git a/patches/expo-updates+0.27.4.patch b/patches/expo-updates+0.27.4.patch deleted file mode 100644 index 6fc4fc5fc..000000000 --- a/patches/expo-updates+0.27.4.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift -index b85291e..546709d 100644 ---- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift -+++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift -@@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update { - status = UpdateStatus.StatusPending - } - -+ // Instead of relying on various hacks to get the correct format for the specific -+ // platform on the backend, we can just add this little patch.. -+ let dateFormatter = DateFormatter() -+ dateFormatter.locale = Locale(identifier: "en_US_POSIX") -+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" -+ let date = dateFormatter.date(from:commitTime) ?? RCTConvert.nsDate(commitTime)! -+ - return Update( - manifest: manifest, - config: config, - database: database, - updateId: uuid, - scopeKey: config.scopeKey, -- commitTime: RCTConvert.nsDate(commitTime), -+ commitTime: date, - runtimeVersion: runtimeVersion, - keep: true, - status: status, diff --git a/patches/expo-updates+0.28.12.patch b/patches/expo-updates+0.28.12.patch new file mode 100644 index 000000000..6fc4fc5fc --- /dev/null +++ b/patches/expo-updates+0.28.12.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift +index b85291e..546709d 100644 +--- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift ++++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift +@@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update { + status = UpdateStatus.StatusPending + } + ++ // Instead of relying on various hacks to get the correct format for the specific ++ // platform on the backend, we can just add this little patch.. ++ let dateFormatter = DateFormatter() ++ dateFormatter.locale = Locale(identifier: "en_US_POSIX") ++ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" ++ let date = dateFormatter.date(from:commitTime) ?? RCTConvert.nsDate(commitTime)! ++ + return Update( + manifest: manifest, + config: config, + database: database, + updateId: uuid, + scopeKey: config.scopeKey, +- commitTime: RCTConvert.nsDate(commitTime), ++ commitTime: date, + runtimeVersion: runtimeVersion, + keep: true, + status: status, diff --git a/patches/expo-updates+0.28.12.patch.md b/patches/expo-updates+0.28.12.patch.md new file mode 100644 index 000000000..6d5d7093d --- /dev/null +++ b/patches/expo-updates+0.28.12.patch.md @@ -0,0 +1,7 @@ +# Expo-Updates Patch + +This is a small patch to convert timestamp formats that are returned from the backend. Instead of relying on the +backend to return the correct format for a specific format (the format required on Android is not the same as on iOS) +we can just add this conversion in. + +Don't remove unless we make changes on the backend to support both platforms. diff --git a/patches/react-native+0.76.6.patch.md b/patches/react-native+0.76.6.patch.md deleted file mode 100644 index eacb9f267..000000000 --- a/patches/react-native+0.76.6.patch.md +++ /dev/null @@ -1,17 +0,0 @@ -# ***This second part of this patch is load bearing, do not remove.*** - -## RefreshControl Patch - iOS 17.4 Haptic Regression - -Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since -17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update -in the RN repo: https://github.com/facebook/react-native/issues/43388 - -## RefreshControl Path - ScrollForwarder - -Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class. -This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that -module. - -## ScrollView centerContent fix - -Includes https://github.com/facebook/react-native/pull/47591 early. Delete when it's in a release. diff --git a/patches/react-native+0.76.9.patch b/patches/react-native+0.76.9.patch deleted file mode 100644 index 7e96d875c..000000000 --- a/patches/react-native+0.76.9.patch +++ /dev/null @@ -1,366 +0,0 @@ -diff --git a/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts b/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts -index 62f52a7..ca30165 100644 ---- a/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts -+++ b/node_modules/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts -@@ -426,9 +426,10 @@ export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle { - */ - pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto' | undefined; - isolation?: 'auto' | 'isolate' | undefined; -- cursor?: CursorValue | undefined; -+ cursor?: CursorValue | string | undefined; - boxShadow?: ReadonlyArray | string | undefined; - filter?: ReadonlyArray | string | undefined; -+ transformOrigin?: string | (string | number)[] | undefined; - } - - export type FontVariant = -@@ -536,7 +537,11 @@ export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle { - textShadowOffset?: {width: number; height: number} | undefined; - textShadowRadius?: number | undefined; - textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | undefined; -- userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all' | undefined; -+ userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all' | string | undefined; -+ cursor?: CursorValue | string | undefined; -+ boxShadow?: ReadonlyArray | string | undefined; -+ filter?: ReadonlyArray | string | undefined; -+ transformOrigin?: string | (string | number)[] | undefined; - } - - /** -@@ -558,5 +563,8 @@ export interface ImageStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle { - tintColor?: ColorValue | undefined; - opacity?: AnimatableNumericValue | undefined; - objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | undefined; -- cursor?: CursorValue | undefined; -+ cursor?: CursorValue | string | undefined; -+ boxShadow?: ReadonlyArray | string | undefined; -+ filter?: ReadonlyArray | string | undefined; -+ transformOrigin?: string | (string | number)[] | undefined; - } -diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm -index 93af874..106f8ec 100644 ---- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm -+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm -@@ -66,28 +66,51 @@ - * ScrollView, we force it to be centered, but when you zoom or the content otherwise - * becomes larger than the ScrollView, there is no padding around the content but it - * can still fill the whole view. -+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/. - */ --- (void)setContentOffset:(CGPoint)contentOffset -+-(void)centerContentIfNeeded - { -- if (_isSetContentOffsetDisabled) { -+ if (!_centerContent) { - return; - } - -- if (_centerContent && !CGSizeEqualToSize(self.contentSize, CGSizeZero)) { -- CGSize scrollViewSize = self.bounds.size; -- if (self.contentSize.width <= scrollViewSize.width) { -- contentOffset.x = -(scrollViewSize.width - self.contentSize.width) / 2.0; -- } -- if (self.contentSize.height <= scrollViewSize.height) { -- contentOffset.y = -(scrollViewSize.height - self.contentSize.height) / 2.0; -- } -+ CGSize contentSize = self.contentSize; -+ CGSize boundsSize = self.bounds.size; -+ if (CGSizeEqualToSize(contentSize, CGSizeZero) || -+ CGSizeEqualToSize(boundsSize, CGSizeZero)) { -+ return; - } - -+ CGFloat top = 0, left = 0; -+ if (contentSize.width < boundsSize.width) { -+ left = (boundsSize.width - contentSize.width) * 0.5f; -+ } -+ if (contentSize.height < boundsSize.height) { -+ top = (boundsSize.height - contentSize.height) * 0.5f; -+ } -+ self.contentInset = UIEdgeInsetsMake(top, left, top, left); -+} -+ -+- (void)setContentOffset:(CGPoint)contentOffset -+{ -+ if (_isSetContentOffsetDisabled) { -+ return; -+ } - super.contentOffset = CGPointMake( - RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"), - RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y")); - } - -+- (void)setFrame:(CGRect)frame { -+ [super setFrame:frame]; -+ [self centerContentIfNeeded]; -+} -+ -+- (void)didAddSubview:(UIView *)subview { -+ [super didAddSubview:subview]; -+ [self centerContentIfNeeded]; -+} -+ - - (BOOL)touchesShouldCancelInContentView:(UIView *)view - { - if ([_overridingDelegate respondsToSelector:@selector(touchesShouldCancelInContentView:)]) { -@@ -257,6 +280,10 @@ - } - } - -+- (void)scrollViewDidZoom:(__unused UIScrollView *)scrollView { -+ [self centerContentIfNeeded]; -+} -+ - #pragma mark - - - - (BOOL)isHorizontal:(UIScrollView *)scrollView -diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h -index e9b330f..ec5f58c 100644 ---- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h -+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h -@@ -15,5 +15,8 @@ - @property (nonatomic, copy) NSString *title; - @property (nonatomic, copy) RCTDirectEventBlock onRefresh; - @property (nonatomic, weak) UIScrollView *scrollView; -+@property (nonatomic, copy) UIColor *customTintColor; -+ -+- (void)forwarderBeginRefreshing; - - @end -diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m -index 53bfd04..ff1b1ed 100644 ---- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m -+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m -@@ -23,6 +23,7 @@ - UIColor *_titleColor; - CGFloat _progressViewOffset; - BOOL _hasMovedToWindow; -+ UIColor *_customTintColor; - } - - - (instancetype)init -@@ -58,6 +59,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - _isInitialRender = false; - } - -+- (void)didMoveToSuperview -+{ -+ [super didMoveToSuperview]; -+ [self setTintColor:_customTintColor]; -+} -+ - - (void)didMoveToWindow - { - [super didMoveToWindow]; -@@ -221,4 +228,50 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) - } - } - -+// Fix for https://github.com/facebook/react-native/issues/43388 -+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor -+// is set before the refresh control gets added to the scrollview. We'll call this -+// function whenever the superview changes. We'll also call it if the value of customTintColor -+// changes. -+- (void)setTintColor:(UIColor *)tintColor -+{ -+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) { -+ [super setTintColor:tintColor]; -+ } -+} -+ -+// This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native -+// libraries to perform a refresh of a scrollview and access the refresh control's onRefresh -+// function. -+- (void)forwarderBeginRefreshing -+{ -+ _refreshingProgrammatically = NO; -+ -+ [self sizeToFit]; -+ -+ if (!self.scrollView) { -+ return; -+ } -+ -+ UIScrollView *scrollView = (UIScrollView *)self.scrollView; -+ -+ [UIView animateWithDuration:0.3 -+ delay:0 -+ options:UIViewAnimationOptionBeginFromCurrentState -+ animations:^(void) { -+ // Whenever we call this method, the scrollview will always be at a position of -+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl -+ [scrollView setContentOffset:CGPointMake(0, -65)]; -+ } -+ completion:^(__unused BOOL finished) { -+ [super beginRefreshing]; -+ [self setCurrentRefreshingState:super.refreshing]; -+ -+ if (self->_onRefresh) { -+ self->_onRefresh(nil); -+ } -+ } -+ ]; -+} -+ - @end -diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m -index 40aaf9c..1c60164 100644 ---- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m -+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m -@@ -22,11 +22,12 @@ RCT_EXPORT_MODULE() - - RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock) - RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL) --RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) - RCT_EXPORT_VIEW_PROPERTY(title, NSString) - RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor) - RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat) - -+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor) -+ - RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing) - { - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { -diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m -index 6f41b5c..9b4f77f 100644 ---- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m -+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m -@@ -159,26 +159,8 @@ - return !shouldDisableScrollInteraction; - } - --/* -- * Automatically centers the content such that if the content is smaller than the -- * ScrollView, we force it to be centered, but when you zoom or the content otherwise -- * becomes larger than the ScrollView, there is no padding around the content but it -- * can still fill the whole view. -- */ - - (void)setContentOffset:(CGPoint)contentOffset - { -- UIView *contentView = [self contentView]; -- if (contentView && _centerContent && !CGSizeEqualToSize(contentView.frame.size, CGSizeZero)) { -- CGSize subviewSize = contentView.frame.size; -- CGSize scrollViewSize = self.bounds.size; -- if (subviewSize.width <= scrollViewSize.width) { -- contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0; -- } -- if (subviewSize.height <= scrollViewSize.height) { -- contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0; -- } -- } -- - super.contentOffset = CGPointMake( - RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"), - RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y")); -@@ -433,6 +415,11 @@ static inline void RCTApplyTransformationAccordingLayoutDirection( - // Does nothing - } - -+- (void)setFrame:(CGRect)frame { -+ [super setFrame:frame]; -+ [self centerContentIfNeeded]; -+} -+ - - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex - { - [super insertReactSubview:view atIndex:atIndex]; -@@ -449,6 +436,8 @@ static inline void RCTApplyTransformationAccordingLayoutDirection( - _contentView = view; - RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection); - [_scrollView addSubview:view]; -+ -+ [self centerContentIfNeeded]; - } - } - -@@ -658,9 +647,46 @@ static inline void RCTApplyTransformationAccordingLayoutDirection( - } - - RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin) --RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll) - RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop) - -+-(void)scrollViewDidZoom : (UIScrollView *)scrollView -+{ -+ [self centerContentIfNeeded]; -+ -+ RCT_SEND_SCROLL_EVENT(onScroll, nil); -+ RCT_FORWARD_SCROLL_EVENT(scrollViewDidZoom : scrollView); -+} -+ -+/* -+ * Automatically centers the content such that if the content is smaller than the -+ * ScrollView, we force it to be centered, but when you zoom or the content otherwise -+ * becomes larger than the ScrollView, there is no padding around the content but it -+ * can still fill the whole view. -+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/. -+ */ -+-(void)centerContentIfNeeded -+{ -+ if (!_scrollView.centerContent) { -+ return; -+ } -+ -+ CGSize contentSize = self.contentSize; -+ CGSize boundsSize = self.bounds.size; -+ if (CGSizeEqualToSize(contentSize, CGSizeZero) || -+ CGSizeEqualToSize(boundsSize, CGSizeZero)) { -+ return; -+ } -+ -+ CGFloat top = 0, left = 0; -+ if (contentSize.width < boundsSize.width) { -+ left = (boundsSize.width - contentSize.width) * 0.5f; -+ } -+ if (contentSize.height < boundsSize.height) { -+ top = (boundsSize.height - contentSize.height) * 0.5f; -+ } -+ _scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left); -+} -+ - - (void)addScrollListener:(NSObject *)scrollListener - { - [_scrollListeners addObject:scrollListener]; -@@ -939,6 +965,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop) - CGSize contentSize = self.contentSize; - if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) { - _scrollView.contentSize = contentSize; -+ [self centerContentIfNeeded]; - } - } - -@@ -1061,6 +1088,22 @@ RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalSc - RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL) - RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat); - -+- (void)setScrollIndicatorInsets:(UIEdgeInsets)value -+{ -+ [_scrollView setScrollIndicatorInsets:value]; -+} -+ -+- (UIEdgeInsets)scrollIndicatorInsets -+{ -+ UIEdgeInsets verticalScrollIndicatorInsets = [_scrollView verticalScrollIndicatorInsets]; -+ UIEdgeInsets horizontalScrollIndicatorInsets = [_scrollView horizontalScrollIndicatorInsets]; -+ return UIEdgeInsetsMake( -+ verticalScrollIndicatorInsets.top, -+ horizontalScrollIndicatorInsets.left, -+ verticalScrollIndicatorInsets.bottom, -+ horizontalScrollIndicatorInsets.right); -+} -+ - - (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0)) - { - // `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13. -diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m -index cd1e7eb..c1d0172 100644 ---- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m -+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m -@@ -83,6 +83,7 @@ RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL) - RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) - RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) - RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) -+RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) - RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets) - RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL) - RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) diff --git a/patches/react-native+0.79.2.patch b/patches/react-native+0.79.2.patch new file mode 100644 index 000000000..609ae6617 --- /dev/null +++ b/patches/react-native+0.79.2.patch @@ -0,0 +1,119 @@ +diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h +index e9b330f..ec5f58c 100644 +--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h ++++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h +@@ -15,5 +15,8 @@ + @property (nonatomic, copy) NSString *title; + @property (nonatomic, copy) RCTDirectEventBlock onRefresh; + @property (nonatomic, weak) UIScrollView *scrollView; ++@property (nonatomic, copy) UIColor *customTintColor; ++ ++- (void)forwarderBeginRefreshing; + + @end +diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m +index 53bfd04..ff1b1ed 100644 +--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m ++++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m +@@ -23,6 +23,7 @@ + UIColor *_titleColor; + CGFloat _progressViewOffset; + BOOL _hasMovedToWindow; ++ UIColor *_customTintColor; + } + + - (instancetype)init +@@ -58,6 +59,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) + _isInitialRender = false; + } + ++- (void)didMoveToSuperview ++{ ++ [super didMoveToSuperview]; ++ [self setTintColor:_customTintColor]; ++} ++ + - (void)didMoveToWindow + { + [super didMoveToWindow]; +@@ -221,4 +228,50 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) + } + } + ++// Fix for https://github.com/facebook/react-native/issues/43388 ++// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor ++// is set before the refresh control gets added to the scrollview. We'll call this ++// function whenever the superview changes. We'll also call it if the value of customTintColor ++// changes. ++- (void)setTintColor:(UIColor *)tintColor ++{ ++ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) { ++ [super setTintColor:tintColor]; ++ } ++} ++ ++// This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native ++// libraries to perform a refresh of a scrollview and access the refresh control's onRefresh ++// function. ++- (void)forwarderBeginRefreshing ++{ ++ _refreshingProgrammatically = NO; ++ ++ [self sizeToFit]; ++ ++ if (!self.scrollView) { ++ return; ++ } ++ ++ UIScrollView *scrollView = (UIScrollView *)self.scrollView; ++ ++ [UIView animateWithDuration:0.3 ++ delay:0 ++ options:UIViewAnimationOptionBeginFromCurrentState ++ animations:^(void) { ++ // Whenever we call this method, the scrollview will always be at a position of ++ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl ++ [scrollView setContentOffset:CGPointMake(0, -65)]; ++ } ++ completion:^(__unused BOOL finished) { ++ [super beginRefreshing]; ++ [self setCurrentRefreshingState:super.refreshing]; ++ ++ if (self->_onRefresh) { ++ self->_onRefresh(nil); ++ } ++ } ++ ]; ++} ++ + @end +diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m +index 40aaf9c..1c60164 100644 +--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m ++++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m +@@ -22,11 +22,12 @@ RCT_EXPORT_MODULE() + + RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock) + RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL) +-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) + RCT_EXPORT_VIEW_PROPERTY(title, NSString) + RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor) + RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat) + ++RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor) ++ + RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing) + { + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { +diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m +index cd1e7eb..c1d0172 100644 +--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m ++++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m +@@ -83,6 +83,7 @@ RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL) + RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) + RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) + RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) ++RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) + RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets) + RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL) + RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) diff --git a/patches/react-native+0.79.2.patch.md b/patches/react-native+0.79.2.patch.md new file mode 100644 index 000000000..9c93aee5c --- /dev/null +++ b/patches/react-native+0.79.2.patch.md @@ -0,0 +1,13 @@ +# ***This second part of this patch is load bearing, do not remove.*** + +## RefreshControl Patch - iOS 17.4 Haptic Regression + +Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since +17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update +in the RN repo: https://github.com/facebook/react-native/issues/43388 + +## RefreshControl Path - ScrollForwarder + +Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class. +This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that +module. diff --git a/patches/react-native-gesture-handler+2.20.2.patch b/patches/react-native-gesture-handler+2.20.2.patch deleted file mode 100644 index 431a9ab3e..000000000 --- a/patches/react-native-gesture-handler+2.20.2.patch +++ /dev/null @@ -1,36 +0,0 @@ -diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm -index 43d11b4..5bf0d32 100644 ---- a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm -+++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm -@@ -461,16 +461,23 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer - - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer - shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer - { -- if ([_handlersToWaitFor count]) { -- RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; -- if (handler != nil) { -- for (NSNumber *handlerTag in _handlersToWaitFor) { -- if ([handler.tag isEqual:handlerTag]) { -- return YES; -- } -- } -+ RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; -+ if (handler == nil) { -+ return NO; -+ } -+ -+ for (NSNumber *handlerTag in _handlersToWaitFor) { -+ if ([handler.tag isEqual:handlerTag]) { -+ return YES; - } - } -+ -+ for (NSNumber *handlerTag in handler->_handlersThatShouldWait) { -+ if ([_tag isEqual:handlerTag]) { -+ return YES; -+ } -+ } -+ - return NO; - } - diff --git a/patches/react-native-gesture-handler+2.25.0.patch b/patches/react-native-gesture-handler+2.25.0.patch new file mode 100644 index 000000000..431a9ab3e --- /dev/null +++ b/patches/react-native-gesture-handler+2.25.0.patch @@ -0,0 +1,36 @@ +diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm +index 43d11b4..5bf0d32 100644 +--- a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm ++++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm +@@ -461,16 +461,23 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer + { +- if ([_handlersToWaitFor count]) { +- RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; +- if (handler != nil) { +- for (NSNumber *handlerTag in _handlersToWaitFor) { +- if ([handler.tag isEqual:handlerTag]) { +- return YES; +- } +- } ++ RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer]; ++ if (handler == nil) { ++ return NO; ++ } ++ ++ for (NSNumber *handlerTag in _handlersToWaitFor) { ++ if ([handler.tag isEqual:handlerTag]) { ++ return YES; + } + } ++ ++ for (NSNumber *handlerTag in handler->_handlersThatShouldWait) { ++ if ([_tag isEqual:handlerTag]) { ++ return YES; ++ } ++ } ++ + return NO; + } + diff --git a/patches/react-native-image-crop-picker+0.41.6.patch b/patches/react-native-image-crop-picker+0.41.6.patch deleted file mode 100644 index 57589931a..000000000 --- a/patches/react-native-image-crop-picker+0.41.6.patch +++ /dev/null @@ -1,47 +0,0 @@ -diff --git a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml -index 391f303..8e2c3db 100644 ---- a/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml -+++ b/node_modules/react-native-image-crop-picker/android/src/main/AndroidManifest.xml -@@ -24,7 +24,7 @@ - - -+ android:theme="@style/Theme.UCropNoEdgeToEdge" /> - - - -diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml -new file mode 100644 -index 0000000..396d5a8 ---- /dev/null -+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values-v35/styles.xml -@@ -0,0 +1,5 @@ -+ -+ -+ -diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml -new file mode 100644 -index 0000000..50129b6 ---- /dev/null -+++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml -@@ -0,0 +1,3 @@ -+ -+ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml +new file mode 100644 +index 0000000..55569aa +--- /dev/null ++++ b/node_modules/react-native-image-crop-picker/android/src/main/res/values/styles.xml +@@ -0,0 +1,3 @@ ++ ++