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:
jjbrunton committed 2026-08-28 20:28:31 +01:00
1 parent aacb4f8731
commit 61ec3692b7
27 files changed
+3867 -5535

No files matched your search

@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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';
+25 -8
View File
@@ -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;
}
+14 -1
View File
@@ -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),