Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- fixed: ramps: Various Infinite UI/UX issues
- fixed: Search keyboard not dismissing when submitting search
- fixed: Auto-correct not disabled for search input
- fixed: In-app review for iOS 18+

## 4.41.1 (2025-12-29)

Expand Down
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3475,7 +3475,7 @@ SPEC CHECKSUMS:
RNSentry: 19b4ee2a31886bcc5a64698a27cf8495cb8f5d17
RNShare: 6300b941668273d502ecee9122cade0d5ea966bd
RNSound: cfeaf6c6a734303c887e04b946cbb7e294bff123
RNStoreReview: 8b47d208282c23296d2466a488a8d9ca1979d79b
RNStoreReview: b3cb7df1405d56afd51301ada4660f6c9b970055
RNSVG: ca807495c5219c05c747254200b89a4e3078db31
RNVectorIcons: f1bc9e04b6f67ec09ea54e6f092e75a9e205c1d7
RNWorklets: b1faafefb82d9f29c4018404a0fb33974b494a7b
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
"react-native-securerandom": "^1.0.1",
"react-native-share": "^12.0.11",
"react-native-sound": "^0.12.0",
"react-native-store-review": "^0.4.3",
"react-native-store-review": "https://github.com/dylancom/react-native-store-review#575f49229de2439095205b3cd17babea31972c95",
"react-native-svg": "^15.12.1",
"react-native-vector-icons": "^10.1.0",
"react-native-vision-camera": "^4.7.2",
Expand Down
27 changes: 27 additions & 0 deletions patches/react-native-store-review+0.4.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/node_modules/react-native-store-review/RNStoreReview.podspec b/node_modules/react-native-store-review/RNStoreReview.podspec
index 1234567..abcdefg 100644
--- a/node_modules/react-native-store-review/RNStoreReview.podspec
+++ b/node_modules/react-native-store-review/RNStoreReview.podspec
@@ -14,6 +14,10 @@ Pod::Spec.new do |s|
s.preserve_paths = "**/*.js"
s.ios.framework = 'StoreKit'
s.requires_arc = true
+ s.pod_target_xcconfig = {
+ 'DEFINES_MODULE' => 'YES',
+ 'SWIFT_VERSION' => '5.0'
+ }

s.dependency "React-Core"

diff --git a/node_modules/react-native-store-review/ios/RNStoreReview.mm b/node_modules/react-native-store-review/ios/RNStoreReview.mm
index 2345678..bcdefgh 100644
--- a/node_modules/react-native-store-review/ios/RNStoreReview.mm
+++ b/node_modules/react-native-store-review/ios/RNStoreReview.mm
@@ -1,6 +1,6 @@
#import "RNStoreReview.h"

-#import "RNStoreReview-Swift.h"
+#import <RNStoreReview/RNStoreReview-Swift.h>

@implementation RNStoreReview

53 changes: 28 additions & 25 deletions src/components/modals/ScanModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { checkAndRequestPermission } from '../services/PermissionsManager'
import { cacheStyles, type Theme, useTheme } from '../services/ThemeContext'
import { EdgeText, Paragraph } from '../themed/EdgeText'
import { ModalFooter } from '../themed/ModalParts'
import { SceneHeader } from '../themed/SceneHeader'
import { SceneHeaderUi4 } from '../themed/SceneHeaderUi4'
import { EdgeModal } from './EdgeModal'

interface Props {
Expand All @@ -52,7 +52,7 @@ interface Props {
textModalTitle?: string
}

export const ScanModal = (props: Props) => {
export const ScanModal: React.FC<Props> = props => {
const {
bridge,
textModalAutoFocus,
Expand All @@ -75,38 +75,43 @@ export const ScanModal = (props: Props) => {
handleBarCodeRead(codes)
}
})
const cameraPermission = useSelector(state => state.permissions.camera)
const reduxCameraPermission = useSelector(state => state.permissions.camera)
const [cameraPermission, setCameraPermission] = React.useState(
reduxCameraPermission
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local permission state doesn't sync with Redux updates

Medium Severity

The cameraPermission local state is initialized from reduxCameraPermission but never syncs with Redux updates after mount. When a user grants camera permission via OS Settings and returns to the app, PermissionsManager updates Redux, but the local cameraPermission state remains stale. This leaves users stuck on the permission-denied view even after granting permission, requiring them to close and reopen the modal.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@swansontec swansontec Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is the wrong solution. We don't want to track state locally in the component - that's the job of the permission subsystem. The problem is that the permission subsystem is modifying things and then forgetting to update redux.

Over in the permissions manager, we should do this:

/**
 * Checks permission and attempts to request permissions (only if checked
 * permission was 'denied')
 */
export const checkAndRequestPermission =
  (permission: Permission): ThunkAction<Promise<PermissionStatus>> =>
  async dispatch => {
    const status: PermissionStatus = await check(permissionNames[permission])

    if (status !== 'denied') return status

    const newStatus = await request(permissionNames[permission])
    if (newStatus !== status) {
      dispatch({
        type: 'PERMISSIONS/UPDATE',
        data: { [permission]: newStatus }
      })
    }
    return newStatus
  }

And then here is just becomes:

- checkAndRequestPermission('camera')
+ dispatch(checkAndRequestPermission('camera'))

And presto! The permissions manager is updating redux so this scene works correctly.

const [torchEnabled, setTorchEnabled] = React.useState(false)
const [scanEnabled, setScanEnabled] = React.useState(false)

const handleFlash = () => {
const handleFlash = (): void => {
triggerHaptic('impactLight')
setTorchEnabled(!torchEnabled)
}

// Mount effects
React.useEffect(() => {
setScanEnabled(true)
checkAndRequestPermission('camera').catch(err => {
showError(err)
})
checkAndRequestPermission('camera')
.then(status => {
setCameraPermission(status)
})
.catch(showError)
return () => {
setScanEnabled(false)
}
}, [])

const handleBarCodeRead = (codes: Code[]) => {
const handleBarCodeRead = (codes: Code[]): void => {
setScanEnabled(false)
triggerHaptic('impactLight')
bridge.resolve(codes[0].value)
}

const handleSettings = async () => {
const handleSettings = async (): Promise<void> => {
triggerHaptic('impactLight')
await Linking.openSettings()
}

const handleTextInput = async () => {
const handleTextInput = async (): Promise<void> => {
triggerHaptic('impactLight')
const uri = await Airship.show<string | undefined>(bridge => (
<TextInputModal
Expand All @@ -124,16 +129,16 @@ export const ScanModal = (props: Props) => {
}
}

const handleAlbum = () => {
const handleAlbum = (): void => {
triggerHaptic('impactLight')
launchImageLibrary(
{
mediaType: 'photo'
},
result => {
if (result.didCancel) return
if (result.didCancel === true) return

if (result.errorMessage) {
if (result.errorMessage != null && result.errorMessage !== '') {
showDevError(result.errorMessage)
return
}
Expand All @@ -157,18 +162,14 @@ export const ScanModal = (props: Props) => {
logActivity(`QR code read from photo library.`)
bridge.resolve(response.values[0])
})
.catch(error => {
showDevError(error)
})
.catch(showDevError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

}
).catch(err => {
showError(err)
})
).catch(showError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

}

const handleClose = () => {
const handleClose = (): void => {
triggerHaptic('impactLight')
// @ts-expect-error
// @ts-expect-error - AirshipBridge expects string | undefined but resolve() with no args is valid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, would bridge.resolve(undefined) allow us to get rid of the // @ts-expect-error altogether?

bridge.resolve()
}

Expand All @@ -186,7 +187,7 @@ export const ScanModal = (props: Props) => {
headerContainerLayout.height +
(peepholeSpaceLayout.height - holeSize) / 2

const renderModalContent = () => {
const renderModalContent = (): React.JSX.Element | null => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.ReactElement is the preferred return type.

if (!scanEnabled) {
return null
}
Expand Down Expand Up @@ -223,7 +224,9 @@ export const ScanModal = (props: Props) => {
style={styles.headerContainer}
onLayout={handleLayoutHeaderContainer}
>
<SceneHeader title={scanModalTitle} underline withTopMargin />
{/* This isn't technically a scene, so just using SceneHeaderUi4 directly for simplicity. */}
{/* eslint-disable-next-line @typescript-eslint/no-deprecated */}
<SceneHeaderUi4 title={scanModalTitle} />
</View>
<View
style={[
Expand Down Expand Up @@ -340,8 +343,8 @@ const getStyles = cacheStyles((theme: Theme) => ({
},
headerContainer: {
justifyContent: 'flex-end',
marginBottom: theme.rem(0.5),
marginTop: theme.rem(1)
marginTop: theme.rem(2),
marginLeft: theme.rem(0.5)
},
peepholeSpace: {
flex: 2
Expand Down
3 changes: 3 additions & 0 deletions src/components/scenes/HomeScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { View } from 'react-native'
import FastImage from 'react-native-fast-image'
import Animated from 'react-native-reanimated'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { requestReview } from 'react-native-store-review/src'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong import bypasses review logic, breaks Android

High Severity

The requestReview function is imported directly from react-native-store-review/src instead of using the existing wrapper in src/actions/RequestReviewActions.tsx. The native module only works on iOS, leaving Android users without in-app review functionality. Additionally, this bypasses the app's sophisticated review trigger system that tracks user actions, respects cooldown periods, and provides Android fallback handling with a modal dialog.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what Cursor said. Is this a debugging commit for QA? In that case, just be sure to drop it before merge.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is for the wrong branch - this is the feta test build to allow QA to test easily.


import { SCROLL_INDICATOR_INSET_FIX } from '../../constants/constantSettings'
import { ENV } from '../../env'
Expand Down Expand Up @@ -146,6 +147,8 @@ export const HomeScene: React.FC<Props> = props => {
setVideoPosts(
filterContentPosts(infoServerData.rollup?.videoPosts, countryCode)
)

requestReview()
}, [countryCode])

const buyCryptoIcon = React.useMemo(
Expand Down
5 changes: 2 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16241,10 +16241,9 @@ react-native-sound@^0.12.0:
resolved "https://registry.yarnpkg.com/react-native-sound/-/react-native-sound-0.12.0.tgz#c1502e7760efa7ca06c38b8ae0435518eae33ef8"
integrity sha512-uOtM/PJ8+xmuXwgS2WMYEcokE/oFE8MV6glZHmB5RYHZ+yzD/0Z7cDL+QJpetTYBbEFOirfcE42o6YfPMJfJfg==

react-native-store-review@^0.4.3:
"react-native-store-review@https://github.com/dylancom/react-native-store-review#575f49229de2439095205b3cd17babea31972c95":
version "0.4.3"
resolved "https://registry.yarnpkg.com/react-native-store-review/-/react-native-store-review-0.4.3.tgz#a524374b1a897b1de1b05d6257640296f1693e3c"
integrity sha512-RSQ6vx2j4p41GwTqNv2VV7yold62j5qDbGEBAjFi6gkXMrMpxFMg+82FPjbh6012tqv6Ebzwfqw6S4m4d7sddw==
resolved "https://github.com/dylancom/react-native-store-review#575f49229de2439095205b3cd17babea31972c95"

react-native-svg-transformer@^1.5.1:
version "1.5.1"
Expand Down
Loading