diff --git a/apps/native-component-list/App.tsx b/apps/native-component-list/App.tsx index 4457575204ed2..41157401779ef 100644 --- a/apps/native-component-list/App.tsx +++ b/apps/native-component-list/App.tsx @@ -5,6 +5,8 @@ import { Platform, StatusBar } from 'react-native'; import RootNavigation from './src/navigation/RootNavigation'; import loadAssetsAsync from './src/utilities/loadAssetsAsync'; +SplashScreen.preventAutoHideAsync(); + function useSplashScreen(loadingFunction: () => void) { const [isLoadingCompleted, setLoadingComplete] = React.useState(false); @@ -12,7 +14,6 @@ function useSplashScreen(loadingFunction: () => void) { React.useEffect(() => { async function loadAsync() { try { - await SplashScreen.preventAutoHideAsync(); await loadingFunction(); } catch (e) { // We might want to provide this error information to an error reporting service diff --git a/apps/native-component-list/app.json b/apps/native-component-list/app.json index d2fde86cdcbd0..3bc77d524b414 100644 --- a/apps/native-component-list/app.json +++ b/apps/native-component-list/app.json @@ -30,6 +30,9 @@ "updates": { "url": "https://u.expo.dev/2c28de10-a2cd-11e6-b8ce-59d1587e6774" }, + "web": { + "bundler": "metro" + }, "facebookScheme": "fb1696089354000816", "facebookAppId": "1696089354000816", "facebookDisplayName": "Expo APIs", diff --git a/apps/native-component-list/index.js b/apps/native-component-list/index.js index ff8a0d4cf9f81..b975ba3a8a3f7 100644 --- a/apps/native-component-list/index.js +++ b/apps/native-component-list/index.js @@ -1,3 +1,4 @@ +import '@expo/metro-runtime'; import { registerRootComponent } from 'expo'; import App from './App'; diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 55232e37c0e9d..6d1faa2a8d7cb 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -6,14 +6,13 @@ "private": true, "main": "index.js", "scripts": { - "build:web": "expo export:web", - "web": "expo start --web --https", - "eject": "SDK_VERSION=43.0.0 expo prebuild", + "build:web": "expo export -p web", + "web": "expo start --web", "lint": "eslint .", "tsc": "tsc --noEmit -p ./tsconfig.json", "start": "expo start", - "ios": "react-native run-ios", - "android": "react-native run-android" + "ios": "expo run:ios", + "android": "expo run:android" }, "expo": { "autolinking": { @@ -153,7 +152,6 @@ }, "devDependencies": { "@babel/core": "^7.23.7", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", "@types/fbemitter": "^2.0.32", "@types/i18n-js": "^3.0.1", "@types/pixi.js": "^4.8.6", @@ -161,7 +159,6 @@ "@types/three": "^0.137.0", "@types/victory": "^31.0.14", "babel-jest": "^29.2.1", - "babel-preset-expo": "~10.0.0", "expo-module-scripts": "^3.0.0", "jest": "^29.2.1", "react-test-renderer": "18.2.0" diff --git a/apps/native-component-list/src/components/HeaderIconButton.tsx b/apps/native-component-list/src/components/HeaderIconButton.tsx index 46eac6e013294..aed380c3e3a73 100644 --- a/apps/native-component-list/src/components/HeaderIconButton.tsx +++ b/apps/native-component-list/src/components/HeaderIconButton.tsx @@ -1,5 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; -import React from 'react'; +import Ionicons from '@expo/vector-icons/Ionicons'; import { StyleSheet, TouchableOpacity, TouchableOpacityProps } from 'react-native'; type Props = TouchableOpacityProps & { diff --git a/apps/native-component-list/src/components/SearchBar.ios.tsx b/apps/native-component-list/src/components/SearchBar.ios.tsx index c1736436857ba..7abc04e7870d8 100644 --- a/apps/native-component-list/src/components/SearchBar.ios.tsx +++ b/apps/native-component-list/src/components/SearchBar.ios.tsx @@ -1,4 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons'; import { useNavigation } from '@react-navigation/native'; import React from 'react'; import { diff --git a/apps/native-component-list/src/components/SearchBar.tsx b/apps/native-component-list/src/components/SearchBar.tsx index 9b3af6c68fa75..ee830d1c10df6 100644 --- a/apps/native-component-list/src/components/SearchBar.tsx +++ b/apps/native-component-list/src/components/SearchBar.tsx @@ -1,4 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons'; import React from 'react'; import { StyleSheet, TextInput, TextStyle, TouchableOpacity, View, Platform } from 'react-native'; diff --git a/apps/native-component-list/src/components/ShowActionSheetButton.tsx b/apps/native-component-list/src/components/ShowActionSheetButton.tsx index 39415d26ff673..cc51597f2ab5e 100644 --- a/apps/native-component-list/src/components/ShowActionSheetButton.tsx +++ b/apps/native-component-list/src/components/ShowActionSheetButton.tsx @@ -1,45 +1,33 @@ import { ActionSheetOptions } from '@expo/react-native-action-sheet'; -import MaterialCommunityIcons from '@expo/vector-icons/build/MaterialCommunityIcons'; -import React from 'react'; +import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; import { Text, TextStyle, View } from 'react-native'; const icon = (name: string) => ; -interface Props { +// A custom button that shows examples of different share sheet configurations +export default function ShowActionSheetButton({ + title, + withTitle = false, + withMessage = false, + withIcons = false, + withSeparators = false, + withCustomStyles = false, + onSelection = null, + showActionSheetWithOptions, +}: { title: string; showActionSheetWithOptions: ( options: ActionSheetOptions, onSelection: (index: number) => void ) => void; - onSelection: (index: number) => void; + onSelection: ((index: number) => void) | null; withTitle?: boolean; withMessage?: boolean; withIcons?: boolean; withSeparators?: boolean; withCustomStyles?: boolean; -} - -// A custom button that shows examples of different share sheet configurations -export default class ShowActionSheetButton extends React.PureComponent { - static defaultProps = { - withTitle: false, - withMessage: false, - withIcons: false, - withSeparators: false, - withCustomStyles: false, - onSelection: null, - }; - - _showActionSheet = () => { - const { - withTitle, - withMessage, - withIcons, - withSeparators, - withCustomStyles, - onSelection, - showActionSheetWithOptions, - } = this.props; +}) { + const showActionSheet = () => { // Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html const options = ['Delete', 'Save', 'Share', 'Cancel']; const icons = withIcons @@ -82,28 +70,25 @@ export default class ShowActionSheetButton extends React.PureComponent { }, (buttonIndex) => { // Do something here depending on the button index selected - onSelection(buttonIndex); + onSelection?.(buttonIndex); } ); }; - render() { - const { title } = this.props; - return ( - - - - {title} - - - - ); - } + return ( + + + + {title} + + + + ); } diff --git a/apps/native-component-list/src/components/TabIcon.tsx b/apps/native-component-list/src/components/TabIcon.tsx index ec24280a32cce..ce9cbc115a51b 100644 --- a/apps/native-component-list/src/components/TabIcon.tsx +++ b/apps/native-component-list/src/components/TabIcon.tsx @@ -1,4 +1,4 @@ -import MaterialCommunityIcons from '@expo/vector-icons/build/MaterialCommunityIcons'; +import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; import React from 'react'; import { Platform } from 'react-native'; diff --git a/apps/native-component-list/src/navigation/StackConfig.tsx b/apps/native-component-list/src/navigation/StackConfig.tsx index 3dd4f3eeabc21..529c0114b404a 100644 --- a/apps/native-component-list/src/navigation/StackConfig.tsx +++ b/apps/native-component-list/src/navigation/StackConfig.tsx @@ -1,4 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons'; import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { HeaderStyleInterpolators } from '@react-navigation/stack'; import * as React from 'react'; diff --git a/apps/native-component-list/src/screens/AV/Player.tsx b/apps/native-component-list/src/screens/AV/Player.tsx index 5daf849faf449..8ee20c2f1d3d2 100644 --- a/apps/native-component-list/src/screens/AV/Player.tsx +++ b/apps/native-component-list/src/screens/AV/Player.tsx @@ -1,4 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons'; import Slider from '@react-native-community/slider'; import SegmentedControl from '@react-native-segmented-control/segmented-control'; import { AVMetadata } from 'expo-av'; diff --git a/apps/native-component-list/src/screens/AV/Recorder.tsx b/apps/native-component-list/src/screens/AV/Recorder.tsx index bd1642548eca0..317fef630e923 100644 --- a/apps/native-component-list/src/screens/AV/Recorder.tsx +++ b/apps/native-component-list/src/screens/AV/Recorder.tsx @@ -1,4 +1,4 @@ -import Ionicons from '@expo/vector-icons/build/Ionicons'; +import Ionicons from '@expo/vector-icons/Ionicons'; import { Audio } from 'expo-av'; import React from 'react'; import { diff --git a/apps/native-component-list/src/screens/AV/RecordingScreen.tsx b/apps/native-component-list/src/screens/AV/RecordingScreen.tsx index 77815a3d66773..9a8a4df6aed3c 100644 --- a/apps/native-component-list/src/screens/AV/RecordingScreen.tsx +++ b/apps/native-component-list/src/screens/AV/RecordingScreen.tsx @@ -1,46 +1,33 @@ -import React from 'react'; -import { PixelRatio, ScrollView, StyleSheet } from 'react-native'; +import { useState } from 'react'; +import { ScrollView, StyleSheet } from 'react-native'; import AudioModeSelector from './AudioModeSelector'; import Player from './AudioPlayer'; import Recorder from './Recorder'; import HeadingText from '../../components/HeadingText'; -interface State { - recordingUri?: string; +export default function RecordingScreen() { + const [recordingUri, setRecordingUri] = useState(undefined); + + return ( + + Audio mode + + Recorder + setRecordingUri(recordingUri)} /> + {recordingUri && ( + <> + Last recording + + + )} + + ); } -// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 -// eslint-disable-next-line @typescript-eslint/ban-types -export default class RecordingScreen extends React.Component<{}, State> { - static navigationOptions = { - title: 'Audio Recording', - }; - - readonly state: State = {}; - - _handleRecordingFinished = (recordingUri: string) => this.setState({ recordingUri }); - - _maybeRenderLastRecording = () => - this.state.recordingUri ? ( - <> - Last recording - - - ) : null; - - render() { - return ( - - Audio mode - - Recorder - - {this._maybeRenderLastRecording()} - - ); - } -} +RecordingScreen.navigationOptions = { + title: 'Audio Recording', +}; const styles = StyleSheet.create({ contentContainer: { @@ -65,7 +52,7 @@ const styles = StyleSheet.create({ marginHorizontal: 30, }, player: { - borderBottomWidth: 1.0 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#cccccc', }, }); diff --git a/apps/native-component-list/src/screens/AppearanceScreen.tsx b/apps/native-component-list/src/screens/AppearanceScreen.tsx index 9719a5de8b006..c2a983f40a4e6 100644 --- a/apps/native-component-list/src/screens/AppearanceScreen.tsx +++ b/apps/native-component-list/src/screens/AppearanceScreen.tsx @@ -1,54 +1,25 @@ -import React from 'react'; -import { Appearance, StyleSheet, Text, View } from 'react-native'; -import type { NativeEventSubscription } from 'react-native'; +import { StyleSheet, Text, View, useColorScheme } from 'react-native'; -type ColorSchemeName = Appearance.AppearancePreferences['colorScheme']; +export default function AppearanceScreen() { + const colorScheme = useColorScheme(); -interface State { - colorScheme: ColorSchemeName; -} - -// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 -// eslint-disable-next-line @typescript-eslint/ban-types -export default class AppearanceScreen extends React.Component<{}, State> { - static navigationOptions = { - title: 'Appearance', - }; - - subscription?: NativeEventSubscription; - - state: State = { - colorScheme: Appearance.getColorScheme(), - }; + const isDark = colorScheme === 'dark'; - componentDidMount() { - this.subscription = Appearance.addChangeListener( - ({ colorScheme }: { colorScheme: ColorSchemeName }) => { - this.setState({ colorScheme }); - } - ); - } + return ( + + + {`Current color scheme: `} - componentWillUnmount() { - if (this.subscription) this.subscription.remove(); - } - - render() { - const { colorScheme } = this.state; - const isDark = colorScheme === 'dark'; - - return ( - - - {`Current color scheme: `} - - {this.state.colorScheme} - - - ); - } + {colorScheme} + + + ); } +AppearanceScreen.navigationOptions = { + title: 'Appearance', +}; + const styles = StyleSheet.create({ screen: { flex: 1, diff --git a/apps/native-component-list/src/screens/CalendarsScreen.tsx b/apps/native-component-list/src/screens/CalendarsScreen.tsx index 0651a34bff475..ffd05d6788ef5 100644 --- a/apps/native-component-list/src/screens/CalendarsScreen.tsx +++ b/apps/native-component-list/src/screens/CalendarsScreen.tsx @@ -1,6 +1,6 @@ -import { StackNavigationProp } from '@react-navigation/stack'; +import type { StackNavigationProp } from '@react-navigation/stack'; import * as Calendar from 'expo-calendar'; -import React from 'react'; +import { useState } from 'react'; import { Alert, Platform, ScrollView, StyleSheet, View } from 'react-native'; import Button from '../components/Button'; @@ -44,76 +44,33 @@ const CalendarRow = (props: { ); }; -CalendarRow.navigationOptions = { - title: 'Calendars', -}; - -interface State { - haveCalendarPermissions: boolean; - haveReminderPermissions: boolean; - calendars: Calendar.Calendar[]; - activeCalendarId?: string; - activeCalendarEvents: Calendar.Event[]; - showAddNewEventForm: boolean; - editingEvent?: Calendar.Event; -} - -export default class CalendarsScreen extends React.Component< - { navigation: StackNavigation }, - State -> { - static navigationOptions = { - title: 'Calendars', - }; - - readonly state: State = { - haveCalendarPermissions: false, - haveReminderPermissions: false, - calendars: [], - activeCalendarEvents: [], - showAddNewEventForm: false, - }; - _askForCalendarPermissions = async () => { - const response = await Calendar.requestCalendarPermissionsAsync(); - const granted = response.status === 'granted'; - this.setState({ - haveCalendarPermissions: granted, - }); - return granted; - }; +export default function CalendarsScreen({ navigation }: { navigation: StackNavigation }) { + const [, askForCalendarPermissions] = Calendar.useCalendarPermissions(); + const [, askForReminderPermissions] = Calendar.useRemindersPermissions(); - _askForReminderPermissions = async () => { - if (Platform.OS === 'android') return true; - const response = await Calendar.requestRemindersPermissionsAsync(); - const granted = response.status === 'granted'; - this.setState({ - haveReminderPermissions: granted, - }); - return granted; - }; + const [calendars, setCalendars] = useState([]); - _findCalendars = async () => { - const calendarGranted = await this._askForCalendarPermissions(); - const reminderGranted = await this._askForReminderPermissions(); + const findCalendars = async () => { + const calendarGranted = (await askForCalendarPermissions()).granted; + const reminderGranted = (await askForReminderPermissions()).granted; if (calendarGranted && reminderGranted) { const eventCalendars = (await Calendar.getCalendarsAsync('event')) as unknown as any[]; const reminderCalendars = ( Platform.OS === 'ios' ? await Calendar.getCalendarsAsync('reminder') : [] ) as any[]; - this.setState({ calendars: [...eventCalendars, ...reminderCalendars] }); + setCalendars([...eventCalendars, ...reminderCalendars]); } }; - _addCalendar = async () => { + const addCalendar = async () => { const sourceDetails = Platform.select({ default: () => ({}), ios: () => ({ - sourceId: this.state.calendars.find((cal) => cal.source && cal.source.name === 'Default') - ?.source.id, + sourceId: calendars.find((cal) => cal.source && cal.source.name === 'Default')?.source.id, }), android: () => { - const calendar = this.state.calendars.find( + const calendar = calendars.find( (cal) => cal.accessLevel === Calendar.CalendarAccessLevel.OWNER ); return calendar ? { source: calendar.source, ownerAccount: calendar.ownerAccount } : {}; @@ -130,26 +87,26 @@ export default class CalendarsScreen extends React.Component< try { await Calendar.createCalendarAsync(newCalendar); Alert.alert('Calendar saved successfully'); - this._findCalendars(); + findCalendars(); } catch (e) { Alert.alert('Calendar not saved successfully', e.message); } }; - _updateCalendar = async (calendarId: string) => { + const updateCalendar = async (calendarId: string) => { const newCalendar = { title: 'cool updated calendar', }; try { await Calendar.updateCalendarAsync(calendarId, newCalendar); Alert.alert('Calendar saved successfully'); - this._findCalendars(); + findCalendars(); } catch (e) { Alert.alert('Calendar not saved successfully', e.message); } }; - _deleteCalendar = async (calendar: any) => { + const deleteCalendar = async (calendar: any) => { Alert.alert(`Are you sure you want to delete ${calendar.title}?`, 'This cannot be undone.', [ { text: 'Cancel', @@ -157,11 +114,11 @@ export default class CalendarsScreen extends React.Component< }, { text: 'OK', - onPress: async () => { + async onPress() { try { await Calendar.deleteCalendarAsync(calendar.id); Alert.alert('Calendar deleted successfully'); - this._findCalendars(); + findCalendars(); } catch (e) { Alert.alert('Calendar not deleted successfully', e.message); } @@ -170,32 +127,38 @@ export default class CalendarsScreen extends React.Component< ]); }; - render() { - if (this.state.calendars.length) { - return ( - - - - - - - - - - - - - - {this.state.ready && this._renderImage()} - - - - - - - ); - } - - _renderImage = () => { - const height = - this.state.image?.height && this.state.image?.height < 300 ? this.state.image?.height : 300; - const width = - this.state.image?.width && this.state.image?.width < 300 ? this.state.image?.width : 300; + const renderImage = () => { + const height = state.image?.height && state.image?.height < 300 ? state.image?.height : 300; + const width = state.image?.width && state.image?.width < 300 ? state.image?.width : 300; return ( ); }; - _pickPhoto = async () => { + const pickPhoto = async () => { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { alert('Permission to MEDIA_LIBRARY not granted!'); @@ -127,71 +68,110 @@ export default class ImageManipulatorScreen extends React.Component<{}, State> { alert('No image selected!'); return; } - this.setState({ image: result.assets[0] }); + setState({ image: result.assets[0] }); }; - _rotate = async (deg: number) => { - await this._manipulate([{ rotate: deg }], { + const rotate = async (deg: number) => + await manipulate([{ rotate: deg }], { format: ImageManipulator.SaveFormat.PNG, }); - }; - _resize = async (size: { width?: number; height?: number }) => { - await this._manipulate([{ resize: size }]); - }; + const resize = async (size: { width?: number; height?: number }) => + await manipulate([{ resize: size }]); - _flip = async (flip: ImageManipulator.FlipType) => { - await this._manipulate([{ flip }]); - }; + const flip = async (flip: ImageManipulator.FlipType) => await manipulate([{ flip }]); - _compress = async (compress: number) => { - await this._manipulate([], { compress }); - }; + const compress = async (compress: number) => await manipulate([], { compress }); - _crop = async () => { - await this._manipulate([ + const crop = async () => + await manipulate([ { crop: { originX: 0, originY: 0, - width: this.state.image!.width! / 2, - height: this.state.image!.height!, + width: state.image!.width! / 2, + height: state.image!.height!, }, }, ]); - }; - _combo = async () => { - await this._manipulate([ + const combo = async () => + await manipulate([ { rotate: 180 }, { flip: ImageManipulator.FlipType.Vertical }, { crop: { - originX: this.state.image!.width! / 4, - originY: this.state.image!.height! / 4, - width: this.state.image!.width! / 2, - height: this.state.image!.width! / 2, + originX: state.image!.width! / 4, + originY: state.image!.height! / 4, + width: state.image!.width! / 2, + height: state.image!.width! / 2, }, }, ]); - }; - _reset = () => { - this.setState((state) => ({ image: state.original })); - }; + const reset = () => setState({ image: state.original }); - _manipulate = async ( + const manipulate = async ( actions: ImageManipulator.Action[], saveOptions?: ImageManipulator.SaveOptions ) => { - const { image } = this.state; + const { image } = state; const manipResult = await ImageManipulator.manipulateAsync( (image! as Asset).localUri || image!.uri, actions, saveOptions ); - this.setState({ image: manipResult }); + setState({ image: manipResult }); }; + + return ( + + + + + + + + + + + + + + + + {state.ready && renderImage()} + + + + + + + ); } const Button: React.FunctionComponent = ({ onPress, style, children }) => ( diff --git a/apps/native-component-list/src/screens/KeepAwakeScreen.tsx b/apps/native-component-list/src/screens/KeepAwakeScreen.tsx index 4436d88f8fd12..67dd5c52f2832 100644 --- a/apps/native-component-list/src/screens/KeepAwakeScreen.tsx +++ b/apps/native-component-list/src/screens/KeepAwakeScreen.tsx @@ -1,28 +1,25 @@ import * as KeepAwake from 'expo-keep-awake'; -import React from 'react'; import { View } from 'react-native'; import Button from '../components/Button'; -export default class KeepAwakeScreen extends React.Component { - static navigationOptions = { - title: 'KeepAwake', - }; - - _activate = () => { +export default function KeepAwakeScreen() { + const _activate = () => { KeepAwake.activateKeepAwakeAsync(); }; - _deactivate = () => { + const _deactivate = () => { KeepAwake.deactivateKeepAwake(); }; - render() { - return ( - -