mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
chore(android): target API 36 with Expo 54
Upgrade the mobile native toolchain and Expo dependencies for Android 16 compatibility while preserving web and admin runtime isolation.
This commit is contained in:
1 parent
aacb4f8731
commit
61ec3692b7
27 files changed
+3867
-5535
No files matched your search
@@ -14,6 +14,7 @@ react {
|
||||
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
|
||||
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
|
||||
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
|
||||
// Use Expo CLI to bundle the app, this ensures the Metro config
|
||||
// works correctly with Expo projects.
|
||||
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
|
||||
@@ -63,9 +64,9 @@ react {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
* Set this to true in release builds to optimize the app using R8.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
@@ -78,7 +79,7 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
@@ -93,6 +94,7 @@ android {
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 46
|
||||
versionName "1.0.5"
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -110,15 +112,18 @@ android {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
|
||||
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
|
||||
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
@@ -156,15 +161,15 @@ dependencies {
|
||||
|
||||
if (isGifEnabled) {
|
||||
// For animated gif support
|
||||
implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
|
||||
if (isWebpEnabled) {
|
||||
// For webp support
|
||||
implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
|
||||
if (isWebpAnimatedEnabled) {
|
||||
// Animated webp support
|
||||
implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,4 +181,4 @@ dependencies {
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
@@ -2,6 +2,10 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
@@ -17,7 +21,7 @@
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:requestLegacyExternalStorage="true" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules">
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:requestLegacyExternalStorage="true" android:fullBackupContent="@xml/secure_store_backup_rules" android:dataExtractionRules="@xml/secure_store_data_extraction_rules" android:enableOnBackInvokedCallback="false">
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notification_icon"/>
|
||||
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
|
||||
|
||||
@@ -8,10 +8,10 @@ import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
||||
import com.facebook.soloader.SoLoader
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
@@ -42,11 +42,12 @@ class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, OpenSourceMergedSoMapping)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ReleaseLevel.STABLE
|
||||
}
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:editTextStyle">@style/ResetEditText</item>
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0'
|
||||
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24')
|
||||
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35')
|
||||
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34')
|
||||
kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'
|
||||
|
||||
ndkVersion = "26.1.10909125"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
@@ -19,23 +10,15 @@ buildscript {
|
||||
classpath 'com.google.gms:google-services:4.4.1'
|
||||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
|
||||
}
|
||||
maven {
|
||||
// Android JSC is installed from npm
|
||||
url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
|
||||
@@ -35,7 +35,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
# your application. You should enable this flag either if you want
|
||||
# to write custom TurboModules/Fabric components OR use libraries that
|
||||
# are providing them.
|
||||
newArchEnabled=false
|
||||
newArchEnabled=true
|
||||
|
||||
# Use this property to enable or disable the Hermes JS engine.
|
||||
# If set to false, you will be using JSC instead.
|
||||
@@ -55,6 +55,16 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
android.targetSdkVersion=35
|
||||
android.kotlinVersion=1.9.25
|
||||
android.extraMavenRepos=[]
|
||||
android.targetSdkVersion=36
|
||||
android.extraMavenRepos=[]
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
android.compileSdkVersion=36
|
||||
android.buildToolsVersion=36.0.0
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
pluginManagement {
|
||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().toString())
|
||||
def reactNativeGradlePlugin = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
|
||||
}.standardOutput.asText.get().trim()
|
||||
).getParentFile().absolutePath
|
||||
includeBuild(reactNativeGradlePlugin)
|
||||
|
||||
def expoPluginsPath = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
|
||||
}.standardOutput.asText.get().trim(),
|
||||
"../android/expo-gradle-plugin"
|
||||
).absolutePath
|
||||
includeBuild(expoPluginsPath)
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.facebook.react.settings")
|
||||
id("expo-autolinking-settings")
|
||||
}
|
||||
plugins { id("com.facebook.react.settings") }
|
||||
|
||||
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
|
||||
ex.autolinkLibrariesFromCommand()
|
||||
} else {
|
||||
def command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'android'
|
||||
].toList()
|
||||
ex.autolinkLibrariesFromCommand(command)
|
||||
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'Sauci'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
reactAndroidLibs {
|
||||
from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
|
||||
useExpoModules()
|
||||
expoAutolinking.useExpoModules()
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
include ':app'
|
||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile())
|
||||
includeBuild(expoAutolinking.reactNativeGradlePlugin)
|
||||
@@ -7,7 +7,7 @@
|
||||
"icon": "./assets/icon.png",
|
||||
"scheme": "app.sauci",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": false,
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
@@ -35,7 +35,7 @@
|
||||
"NSPhotoLibraryAddUsageDescription": "Sauci saves photos and videos from your partner's messages to your photo library when you tap the save button.",
|
||||
"NSMicrophoneUsageDescription": "Sauci uses your microphone to record audio when you capture videos to share privately with your partner."
|
||||
},
|
||||
"privacyManifest": {
|
||||
"privacyManifests": {
|
||||
"NSPrivacyTracking": false,
|
||||
"NSPrivacyCollectedDataTypes": [
|
||||
{
|
||||
@@ -205,8 +205,9 @@
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"targetSdkVersion": 35,
|
||||
"kotlinVersion": "1.9.25",
|
||||
"buildToolsVersion": "36.0.0",
|
||||
"compileSdkVersion": 36,
|
||||
"targetSdkVersion": 36,
|
||||
"extraProguardRules": "-keep class com.facebook.hermes.unicode.** { *; }",
|
||||
"useLegacyPackaging": false,
|
||||
"manifestPlaceholders": {
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function AppLayout() {
|
||||
const { initializeRevenueCat, refreshSubscriptionStatus } = useSubscriptionStore();
|
||||
const { fetchStreak } = useStreakStore();
|
||||
const messageToastAnim = useRef(new Animated.Value(-100)).current;
|
||||
const messageTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const messageTimeoutRef = useRef<number | null>(null);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const hasCheckedInitialBiometric = useRef(false);
|
||||
const wentToBackgroundAt = useRef<number | null>(null);
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"node": "20.10.0",
|
||||
"node": "20.19.4",
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"node": "20.10.0",
|
||||
"node": "20.19.4",
|
||||
"distribution": "internal",
|
||||
"env": {
|
||||
"EXPO_PUBLIC_API_URL": "https://api.preprod.sauci.app"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"node": "20.10.0",
|
||||
"node": "20.19.4",
|
||||
"autoIncrement": true,
|
||||
"env": {
|
||||
"EXPO_PUBLIC_API_URL": "https://api.sauci.app",
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
const path = require('path');
|
||||
|
||||
// Find the project and workspace directories
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, '../..');
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
// 1. Watch all files within the monorepo
|
||||
// 1. Watch only relevant folders to improve performance on Windows
|
||||
// watching the entire workspaceRoot is too slow
|
||||
config.watchFolders = [
|
||||
path.resolve(workspaceRoot, 'node_modules'),
|
||||
path.resolve(workspaceRoot, 'packages/shared')
|
||||
];
|
||||
|
||||
// 2. Let Metro know where to resolve packages and in which order
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, 'node_modules'),
|
||||
path.resolve(workspaceRoot, 'node_modules'),
|
||||
];
|
||||
|
||||
// 3. Force Metro to resolve (sub)dependencies from the `node_modules`
|
||||
// of the workspace root, unless they are installed in the project root.
|
||||
config.resolver.disableHierarchicalLookup = true;
|
||||
|
||||
// 4. Keep extraNodeModules in case we need to add monorepo overrides later
|
||||
config.resolver.extraNodeModules = {
|
||||
...config.resolver.extraNodeModules,
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
module.exports = getDefaultConfig(__dirname);
|
||||
+44
-43
@@ -23,8 +23,8 @@
|
||||
"@bacons/apple-targets": "^0.1.6",
|
||||
"@bittingz/expo-widgets": "^3.0.2",
|
||||
"@expo/ngrok": "^4.1.0",
|
||||
"@expo/vector-icons": "~14.0.4",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@expo/vector-icons": "^15.0.3",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/cli": "^20.1.1",
|
||||
"@react-native-firebase/analytics": "^23.8.2",
|
||||
"@react-native-firebase/app": "^23.8.2",
|
||||
@@ -33,63 +33,64 @@
|
||||
"@react-navigation/bottom-tabs": "^7.0.0",
|
||||
"@react-navigation/native": "^7.0.0",
|
||||
"@react-navigation/native-stack": "^7.0.0",
|
||||
"@shopify/react-native-skia": "^1.12.4",
|
||||
"@shopify/react-native-skia": "2.2.12",
|
||||
"@supabase/supabase-js": "^2.39.0",
|
||||
"@tanstack/react-query": "^5.17.0",
|
||||
"babel-preset-expo": "~12.0.0",
|
||||
"babel-preset-expo": "~54.0.12",
|
||||
"base64-arraybuffer": "^1.0.2",
|
||||
"expo": "~52.0.0",
|
||||
"expo-apple-authentication": "~7.1.3",
|
||||
"expo-asset": "~11.0.5",
|
||||
"expo-av": "~15.0.2",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-build-properties": "^0.13.3",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-constants": "~17.0.0",
|
||||
"expo-dev-client": "~5.0.20",
|
||||
"expo-file-system": "~18.0.12",
|
||||
"expo-font": "~13.0.4",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image": "~2.0.7",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linear-gradient": "~14.0.2",
|
||||
"expo-linking": "~7.0.0",
|
||||
"expo-local-authentication": "~15.0.2",
|
||||
"expo-media-library": "~17.0.6",
|
||||
"expo-notifications": "~0.29.0",
|
||||
"expo-router": "~4.0.0",
|
||||
"expo-secure-store": "~14.0.0",
|
||||
"expo-sharing": "~13.0.1",
|
||||
"expo-status-bar": "~2.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-android-widget": "^0.17.2",
|
||||
"expo": "~54.0.0",
|
||||
"expo-apple-authentication": "~8.0.8",
|
||||
"expo-asset": "~12.0.13",
|
||||
"expo-av": "~16.0.8",
|
||||
"expo-blur": "~15.0.8",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-constants": "~18.0.14",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-file-system": "~19.0.24",
|
||||
"expo-font": "~14.0.12",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
"expo-image-picker": "~17.0.11",
|
||||
"expo-linear-gradient": "~15.0.8",
|
||||
"expo-linking": "~8.0.12",
|
||||
"expo-local-authentication": "~17.0.9",
|
||||
"expo-media-library": "~18.2.1",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo-router": "~6.0.24",
|
||||
"expo-secure-store": "~15.0.8",
|
||||
"expo-sharing": "~14.0.8",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-android-widget": "^0.22.1",
|
||||
"react-native-compressor": "^1.13.0",
|
||||
"react-native-gesture-handler": "~2.20.0",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-purchases": "^9.6.12",
|
||||
"react-native-reanimated": "~3.16.0",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-svg": "15.8.0",
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-view-shot": "^4.0.3",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.5.1",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@testing-library/jest-native": "^5.4.3",
|
||||
"@testing-library/react-native": "^13.3.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/react": "~18.3.12",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/react": "~19.1.10",
|
||||
"@types/react-test-renderer": "^19.1.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "~8.0.1",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-expo": "~52.0.0",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"typescript": "^5.3.3"
|
||||
"jest-expo": "~54.0.18",
|
||||
"react-test-renderer": "19.1.0",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react-native';
|
||||
import { Alert } from 'react-native';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { useMediaSaver } from '@/hooks/useMediaSaver';
|
||||
|
||||
// Mock expo-media-library
|
||||
@@ -11,7 +11,7 @@ jest.mock('expo-media-library', () => ({
|
||||
}));
|
||||
|
||||
// Mock expo-file-system with all required methods
|
||||
jest.mock('expo-file-system', () => ({
|
||||
jest.mock('expo-file-system/legacy', () => ({
|
||||
readAsStringAsync: jest.fn(async () => ''),
|
||||
downloadAsync: jest.fn(),
|
||||
deleteAsync: jest.fn(),
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import ViewShot from "react-native-view-shot";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import * as FileSystem from "expo-file-system/legacy";
|
||||
import { colors, gradients, radius, typography, spacing, blur } from "../../theme";
|
||||
|
||||
const logo = require("../../../assets/logo.png");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import Animated, { type AnimatedStyleProp } from 'react-native-reanimated';
|
||||
import Animated, { type AnimatedStyle } from 'react-native-reanimated';
|
||||
import type { ViewStyle } from 'react-native';
|
||||
import type { IntensityLevel } from '@/types';
|
||||
import { HEAT_COLORS } from './constants';
|
||||
@@ -9,7 +9,7 @@ import { styles } from './styles';
|
||||
|
||||
interface IntensitySliderTrackProps {
|
||||
value: IntensityLevel;
|
||||
progressStyle: AnimatedStyleProp<ViewStyle>;
|
||||
progressStyle: AnimatedStyle<ViewStyle>;
|
||||
}
|
||||
|
||||
export function IntensitySliderTrack({ value, progressStyle }: IntensitySliderTrackProps) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import ViewShot from 'react-native-view-shot';
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { colors, gradients, radius, typography, spacing, blur } from '../../../theme';
|
||||
|
||||
const logo = require('../../../../assets/logo.png');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type { SkImage } from '@shopify/react-native-skia';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
|
||||
interface UseCanvasCaptureReturn {
|
||||
makeSnapshot: React.MutableRefObject<(() => SkImage | null) | null>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { Alert, Platform, ActionSheetIOS } from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { uploadMedia } from '../lib/mediaApi';
|
||||
|
||||
export interface UseAvatarPickerOptions {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
|
||||
export function useMediaSaver() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getMediaUrl, mediaId } from './mediaApi';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
// Cache for signed URLs with expiration tracking
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform } from 'react-native';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { decode } from 'base64-arraybuffer';
|
||||
import { ApiError, apiClient, authenticatedFetch } from './apiClient';
|
||||
|
||||
|
||||
@@ -12,20 +12,37 @@ import { useMatchStore } from "../store/matchStore";
|
||||
// Configure notification behavior for foreground
|
||||
// We don't show system alerts since the app handles notifications with in-app toasts
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldShowAlert: false,
|
||||
shouldPlaySound: false,
|
||||
handleNotification: async () => ({
|
||||
shouldShowAlert: false,
|
||||
shouldShowBanner: false,
|
||||
shouldShowList: false,
|
||||
shouldPlaySound: false,
|
||||
shouldSetBadge: false,
|
||||
}),
|
||||
});
|
||||
|
||||
// Types
|
||||
export interface NotificationData {
|
||||
export interface NotificationData {
|
||||
type: "match" | "message" | "match_digest" | "partner_activity" | "nudge" | "weekly_summary";
|
||||
match_id?: string;
|
||||
message_id?: string;
|
||||
count?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const notificationTypes = new Set<NotificationData["type"]>([
|
||||
"match",
|
||||
"message",
|
||||
"match_digest",
|
||||
"partner_activity",
|
||||
"nudge",
|
||||
"weekly_summary",
|
||||
]);
|
||||
|
||||
function isNotificationData(value: unknown): value is NotificationData {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const type = (value as { type?: unknown }).type;
|
||||
return typeof type === "string" && notificationTypes.has(type as NotificationData["type"]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -219,9 +236,9 @@ export async function checkAndRegisterPushToken(userId: string): Promise<boolean
|
||||
export function handleNotificationResponse(
|
||||
response: Notifications.NotificationResponse
|
||||
): void {
|
||||
const data = response.notification.request.content.data as NotificationData;
|
||||
|
||||
if (!data) {
|
||||
const data = response.notification.request.content.data;
|
||||
|
||||
if (!isNotificationData(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,13 +103,26 @@ jest.mock('expo-image-picker', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('expo-file-system', () => ({
|
||||
jest.mock('expo-file-system/legacy', () => ({
|
||||
readAsStringAsync: jest.fn(async () => ''),
|
||||
EncodingType: {
|
||||
Base64: 'base64',
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('expo-av', () => ({
|
||||
Audio: {
|
||||
requestPermissionsAsync: jest.fn(async () => ({ status: 'granted' })),
|
||||
setAudioModeAsync: jest.fn(async () => undefined),
|
||||
Recording: {
|
||||
createAsync: jest.fn(),
|
||||
},
|
||||
RecordingOptionsPresets: {
|
||||
HIGH_QUALITY: {},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-native-compressor', () => ({
|
||||
Video: {
|
||||
compress: jest.fn(async (uri: string) => uri),
|
||||
|
||||
Reference in new issue
Block a user