From 25721129bb7b6f785bf44e05476fd3594ff31d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Tue, 11 Nov 2025 09:51:07 +0100 Subject: [PATCH 1/7] refactor: simplify example menu --- example/src/App.tsx | 139 +++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 86 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index d326dd55..3d93c04a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -15,15 +15,44 @@ import StateMachineInputsExample from './pages/RiveStateMachineInputsExample'; import TextRunExample from './pages/RiveTextRunExample'; import OutOfBandAssets from './pages/OutOfBandAssets'; +const Examples = [ + { + title: 'Rive File Loading Examples', + screenId: 'RiveFileLoading', + component: RiveFileLoadingExample, + }, + { + title: 'Rive Data Binding Example', + screenId: 'RiveDataBinding', + component: DataBindingExample, + }, + { + title: 'Rive Events Example', + screenId: 'RiveEvents', + component: EventsExample, + }, + { + title: 'Rive State Machine Inputs Example', + screenId: 'RiveStateMachineInputs', + component: StateMachineInputsExample, + }, + { + title: 'Rive Text Run Example', + screenId: 'RiveTextRun', + component: TextRunExample, + }, + { + title: 'Out of band assets', + screenId: 'OutOfBandAssets', + component: OutOfBandAssets, + }, + { title: 'Template Page', screenId: 'Template', component: TemplatePage }, +] as const; + type RootStackParamList = { Home: undefined; - RiveFileLoading: undefined; - RiveDataBinding: undefined; - RiveEvents: undefined; - RiveStateMachineInputs: undefined; - RiveTextRun: undefined; - Template: undefined; - OutOfBandAssets: undefined; +} & { + [K in (typeof Examples)[number]['screenId']]: undefined; }; const Stack = createStackNavigator(); @@ -33,50 +62,15 @@ function HomeScreen({ navigation }: { navigation: any }) { Rive React Native Examples - navigation.navigate('RiveFileLoading')} - > - Rive File Loading Examples - - navigation.navigate('RiveDataBinding')} - > - Rive Data Binding Example - - navigation.navigate('RiveEvents')} - > - Rive Events Example - - navigation.navigate('RiveStateMachineInputs')} - > - - Rive State Machine Inputs Example - - - navigation.navigate('RiveTextRun')} - > - Rive Text Run Example - - navigation.navigate('OutOfBandAssets')} - > - Out of band assets - - navigation.navigate('Template')} - > - Template Page - + {Examples.map(({ title, screenId }) => ( + navigation.navigate(screenId)} + > + {title} + + ))} ); @@ -101,41 +95,14 @@ export default function App() { component={HomeScreen} options={{ title: 'Rive Examples' }} /> - - - - - - - + {Examples.map(({ screenId, component, title }) => ( + + ))} ); From 964183a01bda2d005a047b6278d0dd34469853ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 12 Nov 2025 16:07:54 +0100 Subject: [PATCH 2/7] chore: expo-eample --- .gitignore | 2 + expo-example/.gitignore | 43 + expo-example/README.md | 50 + expo-example/app.json | 49 + expo-example/app/(tabs)/_layout.tsx | 49 + expo-example/app/_layout.tsx | 41 + expo-example/app/data-binding.tsx | 414 ++ expo-example/app/index.tsx | 155 + expo-example/app/modal.tsx | 29 + expo-example/app/simple-rive.tsx | 85 + .../assets/images/android-icon-background.png | Bin 0 -> 17549 bytes .../assets/images/android-icon-foreground.png | Bin 0 -> 78796 bytes .../assets/images/android-icon-monochrome.png | Bin 0 -> 4140 bytes expo-example/assets/images/favicon.png | Bin 0 -> 1129 bytes expo-example/assets/images/icon.png | Bin 0 -> 393493 bytes .../assets/images/partial-react-logo.png | Bin 0 -> 5075 bytes expo-example/assets/images/react-logo.png | Bin 0 -> 6341 bytes expo-example/assets/images/react-logo@2x.png | Bin 0 -> 14225 bytes expo-example/assets/images/react-logo@3x.png | Bin 0 -> 21252 bytes expo-example/assets/images/splash-icon.png | Bin 0 -> 17547 bytes expo-example/assets/rive/rating.riv | Bin 0 -> 15838 bytes expo-example/assets/rive/rewards_source.riv | Bin 0 -> 216976 bytes expo-example/babel.config.js | 18 + expo-example/components/external-link.tsx | 30 + expo-example/components/haptic-tab.tsx | 18 + expo-example/components/hello-wave.tsx | 20 + .../components/parallax-scroll-view.tsx | 85 + expo-example/components/themed-text.tsx | 60 + expo-example/components/themed-view.tsx | 22 + expo-example/components/ui/collapsible.tsx | 49 + .../components/ui/icon-symbol.ios.tsx | 32 + expo-example/components/ui/icon-symbol.tsx | 51 + expo-example/constants/theme.ts | 54 + expo-example/eslint.config.js | 10 + expo-example/hooks/use-color-scheme.ts | 1 + expo-example/hooks/use-color-scheme.web.ts | 21 + expo-example/hooks/use-theme-color.ts | 21 + expo-example/metro.config.js | 14 + expo-example/package.json | 50 + expo-example/react-native.config.js | 22 + expo-example/scripts/reset-project.js | 112 + expo-example/tsconfig.json | 15 + package.json | 7 +- yarn.lock | 4287 ++++++++++++++++- 44 files changed, 5780 insertions(+), 136 deletions(-) create mode 100644 expo-example/.gitignore create mode 100644 expo-example/README.md create mode 100644 expo-example/app.json create mode 100644 expo-example/app/(tabs)/_layout.tsx create mode 100644 expo-example/app/_layout.tsx create mode 100644 expo-example/app/data-binding.tsx create mode 100644 expo-example/app/index.tsx create mode 100644 expo-example/app/modal.tsx create mode 100644 expo-example/app/simple-rive.tsx create mode 100644 expo-example/assets/images/android-icon-background.png create mode 100644 expo-example/assets/images/android-icon-foreground.png create mode 100644 expo-example/assets/images/android-icon-monochrome.png create mode 100644 expo-example/assets/images/favicon.png create mode 100644 expo-example/assets/images/icon.png create mode 100644 expo-example/assets/images/partial-react-logo.png create mode 100644 expo-example/assets/images/react-logo.png create mode 100644 expo-example/assets/images/react-logo@2x.png create mode 100644 expo-example/assets/images/react-logo@3x.png create mode 100644 expo-example/assets/images/splash-icon.png create mode 100644 expo-example/assets/rive/rating.riv create mode 100644 expo-example/assets/rive/rewards_source.riv create mode 100644 expo-example/babel.config.js create mode 100644 expo-example/components/external-link.tsx create mode 100644 expo-example/components/haptic-tab.tsx create mode 100644 expo-example/components/hello-wave.tsx create mode 100644 expo-example/components/parallax-scroll-view.tsx create mode 100644 expo-example/components/themed-text.tsx create mode 100644 expo-example/components/themed-view.tsx create mode 100644 expo-example/components/ui/collapsible.tsx create mode 100644 expo-example/components/ui/icon-symbol.ios.tsx create mode 100644 expo-example/components/ui/icon-symbol.tsx create mode 100644 expo-example/constants/theme.ts create mode 100644 expo-example/eslint.config.js create mode 100644 expo-example/hooks/use-color-scheme.ts create mode 100644 expo-example/hooks/use-color-scheme.web.ts create mode 100644 expo-example/hooks/use-theme-color.ts create mode 100644 expo-example/metro.config.js create mode 100644 expo-example/package.json create mode 100644 expo-example/react-native.config.js create mode 100755 expo-example/scripts/reset-project.js create mode 100644 expo-example/tsconfig.json diff --git a/.gitignore b/.gitignore index 46c939a7..9745e649 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,8 @@ android/keystores/debug.keystore # Expo .expo/ +expo-example/ios +expo-example/android # Turborepo .turbo/ diff --git a/expo-example/.gitignore b/expo-example/.gitignore new file mode 100644 index 00000000..f8c6c2e8 --- /dev/null +++ b/expo-example/.gitignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +# generated native folders +/ios +/android diff --git a/expo-example/README.md b/expo-example/README.md new file mode 100644 index 00000000..48dd63ff --- /dev/null +++ b/expo-example/README.md @@ -0,0 +1,50 @@ +# Welcome to your Expo app 👋 + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +## Get started + +1. Install dependencies + + ```bash + npm install + ``` + +2. Start the app + + ```bash + npx expo start + ``` + +In the output, you'll find options to open the app in a + +- [development build](https://docs.expo.dev/develop/development-builds/introduction/) +- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) +- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) +- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +## Get a fresh project + +When you're ready, run: + +```bash +npm run reset-project +``` + +This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/expo-example/app.json b/expo-example/app.json new file mode 100644 index 00000000..e6a381ce --- /dev/null +++ b/expo-example/app.json @@ -0,0 +1,49 @@ +{ + "expo": { + "name": "expo-example", + "slug": "expo-example", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "expoexample", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true, + "bundleIdentifier": "com.mfazekas.expo-example" + }, + "android": { + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "backgroundColor": "#000000" + } + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } +} diff --git a/expo-example/app/(tabs)/_layout.tsx b/expo-example/app/(tabs)/_layout.tsx new file mode 100644 index 00000000..7f91124f --- /dev/null +++ b/expo-example/app/(tabs)/_layout.tsx @@ -0,0 +1,49 @@ +import { Tabs } from 'expo-router'; +import React from 'react'; + +import { HapticTab } from '@/components/haptic-tab'; +import { IconSymbol } from '@/components/ui/icon-symbol'; +import { Colors } from '@/constants/theme'; +import { useColorScheme } from '@/hooks/use-color-scheme'; + +export default function TabLayout() { + const colorScheme = useColorScheme(); + + return ( + + , + }} + /> + , + }} + /> + , + }} + /> + , + }} + /> + + ); +} diff --git a/expo-example/app/_layout.tsx b/expo-example/app/_layout.tsx new file mode 100644 index 00000000..d0efcb16 --- /dev/null +++ b/expo-example/app/_layout.tsx @@ -0,0 +1,41 @@ +import { + DarkTheme, + DefaultTheme, + ThemeProvider, +} from '@react-navigation/native'; +import { Stack } from 'expo-router'; +import { StatusBar } from 'expo-status-bar'; +import 'react-native-reanimated'; + +import { useColorScheme } from '@/hooks/use-color-scheme'; + +export default function RootLayout() { + const colorScheme = useColorScheme(); + + return ( + + + + + + + + + + ); +} diff --git a/expo-example/app/data-binding.tsx b/expo-example/app/data-binding.tsx new file mode 100644 index 00000000..6a68302c --- /dev/null +++ b/expo-example/app/data-binding.tsx @@ -0,0 +1,414 @@ +import { + View, + Text, + StyleSheet, + ActivityIndicator, + Button, +} from 'react-native'; +import { useState, useEffect, useRef, useMemo } from 'react'; +import { + Fit, + RiveView, + useRive, + useRiveNumber, + type ViewModelInstance, + useRiveString, + useRiveColor, + useRiveTrigger, + useRiveFile, + type RiveFile, +} from 'react-native-rive'; + +class MyProxyInstance { + original: T; + __dummy: string; + constructor({ + __dummy, + original, + createDefaultInstance, + }: { + __dummy: string; + original: T; + createDefaultInstance?: boolean; + }) { + this.original = original; + this.__dummy = __dummy; + } +} + +// Usage example: +// const dummyInstance = new ProxyInstance({ __dummy: true, original: viewModel.createViewInstance() }); + +function DataBindingExampleOriginal() { + const { riveViewRef, setHybridRef } = useRive(); + const [viewModelInstance, setViewModelInstance] = + useState | null>(null); + const [viewModelError, setViewModelError] = useState(null); + const viewModelInstanceRef = + useRef | null>(null); + + const { riveFile, isLoading, error } = useRiveFile( + require('@/assets/rive/rewards_source.riv') + ); + + // Create view model instance when Rive file is loaded + useEffect(() => { + if (riveFile) { + try { + const viewModel = riveFile.defaultArtboardViewModel(); + if (!viewModel) { + throw new Error('No default artboard view model found'); + } + // + let oinstance = viewModel.createDefaultInstance(); + if (!oinstance) { + throw new Error('Failed to create view model instance'); + } + const instance = new MyProxyInstance({ + __dummy: 'createDefaultInstance', + createDefaultInstance: true, + original: oinstance, + }); + viewModelInstanceRef.current = instance; + setViewModelInstance(instance); + setViewModelError(null); + } catch (err) { + setViewModelError( + err instanceof Error + ? err.message + : 'Failed to create view model instance' + ); + viewModelInstanceRef.current = null; + setViewModelInstance(null); + } + } + }, [riveFile]); + + useEffect(() => { + return () => { + console.log( + 'Disposing view model instance', + viewModelInstanceRef.current + ); + //viewModelInstanceRef.current?.dispose(); + console.log('Disposed view model instance'); + }; + }, []); + + // Bind the view model instance to the RiveView + function bindInstance() { + if (viewModelInstance && riveViewRef) { + try { + console.log('Binding the instance'); + riveViewRef.bindViewModelInstance(viewModelInstance.original); + } catch (err) { + console.error('Failed to bind view model instance:', err); + } + } + } + useEffect(bindInstance, [viewModelInstance, riveViewRef]); + + // Databinding: Number + const { value: coinValue, error: coinValueError } = useRiveNumber( + 'Coin/Item_Value', + viewModelInstance?.original + ); + //useWatchValueDBG(coinValue, 'coinValue'); + + if (coinValueError) { + console.error('coinValueError', coinValueError); + } + + // Databinding: String + const { value: buttonText, setValue: setButtonText } = useRiveString( + 'Button/State_1', + viewModelInstance?.original + ); + //useWatchValueDBG(buttonText, 'buttonText'); + + // Databinding: Color + const { + value: barColor, + setValue: setBarColor, + error: barColorError, + } = useRiveColor('Energy_Bar/Bar_Color', viewModelInstance?.original); + if (barColorError) { + console.error('barColorError', barColorError); + } + //xwuseWatchValueDBG(barColor, 'barColor'); + + // Databinding: Trigger + const { error: triggerError } = useRiveTrigger( + 'Button/Pressed', + viewModelInstance?.original, + { + onTrigger: () => { + console.log('Button pressed'); + }, + } + ); + if (triggerError) { + console.error('triggerError', triggerError); + } + + // Set the initial values of the properties + function mySetInitialValues() { + if (viewModelInstance) { + try { + setButtonText("Let's go!"); + setBarColor('#0000FF'); + } catch (err) { + console.error('Failed to set initial values:', err); + } + } + } + + useEffect(mySetInitialValues, [ + setBarColor, + setButtonText, + viewModelInstance, + ]); + + return ( + + + {error || viewModelError ? ( + {error || viewModelError} + ) : isLoading ? ( + + ) : ( + + )} + {/*false && ( +