Skip to content

Commit ba28760

Browse files
authored
feat(react-native): expo config plugin for dictation (#19)
This PR adds an `Expo` config to be built together with the SDK and ready to be used for the purposes of dictation permissions. It will add both the `Android` permissions to `AndroidManifest.xml` as well as the `iOS` permissions to `Info.plist`. In addition to that, it also adds an `Expo` sample app to be used for testing purposes.
1 parent 6c7915e commit ba28760

40 files changed

+4894
-710
lines changed

.changeset/tame-donkeys-clap.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'ReactNativeChatGPTSample': patch
3+
'expochatgptsample': patch
4+
'@stream-io/chat-react-native-ai': patch
5+
---
6+
7+
- Introduce expo config plugin for dictation permissions
8+
- Fix race condition when no permissions are yet present on ios dictation
9+
- Include Expo sample app as well
10+
- Fix wrong dependency graph resolution between RNCLI and Expo sample apps
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Expo
7+
.expo/
8+
ios/
9+
android/
10+
dist/
11+
web-build/
12+
expo-env.d.ts
13+
14+
# Native
15+
*.orig.*
16+
*.jks
17+
*.p8
18+
*.p12
19+
*.key
20+
*.mobileprovision
21+
22+
# Metro
23+
.metro-health-check*
24+
25+
# debug
26+
npm-debug.*
27+
yarn-debug.*
28+
yarn-error.*
29+
30+
# macOS
31+
.DS_Store
32+
*.pem
33+
34+
# local env files
35+
.env*.local
36+
37+
# typescript
38+
*.tsbuildinfo
39+
40+
app-example
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Expo Skia example
2+
3+
Use [`expo-router`](https://docs.expo.dev/router/introduction/) with [`@shopify/react-native-skia`](https://shopify.github.io/react-native-skia/) to build beautiful, high-performance graphics applications across web and native.
4+
5+
## Launch your own
6+
7+
[![Launch with Expo](https://github.com/expo/examples/blob/master/.gh-assets/launch.svg?raw=true)](https://launch.expo.dev/?github=https://github.com/expo/examples/tree/master/with-skia)
8+
9+
## 🚀 How to use
10+
11+
```sh
12+
npx create-expo-app -e with-skia
13+
```
14+
15+
- Load Skia components with `React.lazy` to ensure they aren't loaded on the server.
16+
- Using a custom suspensy component in `components/async-skia.tsx` to suspend the UI on web until the Skia WASM is fetched and loaded. This ensures errors and pending states are handled in React.
17+
- A postinstall script copies the `canvaskit.wasm` file to the `public` folder for web support. This must be hosted to work on web, use `eas deploy` to push to production.
18+
19+
## Deploy
20+
21+
Deploy on all platforms with Expo Application Services (EAS).
22+
23+
- Deploy the website: `npx eas-cli deploy`[Learn more](https://docs.expo.dev/eas/hosting/get-started/)
24+
- Deploy on iOS and Android using: `npx eas-cli build`[Learn more](https://expo.dev/eas)
25+
26+
## 📝 Notes
27+
28+
- [Expo Router: Docs](https://docs.expo.dev/router/introduction/)
29+
- [React Native Skia: Docs](https://shopify.github.io/react-native-skia/)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"expo": {
3+
"scheme": "skia",
4+
"web": {
5+
"output": "static"
6+
},
7+
"ios": {
8+
"bundleIdentifier": "io.getstream.expochatgptsample",
9+
"appleTeamId": "EHV7XZLAHA"
10+
},
11+
"plugins": [
12+
"expo-router",
13+
[
14+
"expo-image-picker",
15+
{
16+
"photosPermission": "The app accesses your photos to let them share with others.",
17+
"cameraPermission": "The app accesses your camera to let you take photos and share with others."
18+
}
19+
],
20+
[
21+
"@stream-io/chat-react-native-ai",
22+
{
23+
"dictationMicrophoneUsageDescription": "$(PRODUCT_NAME) would like to access your microphone to capture your voice.",
24+
"dictationSpeechRecognitionUsageDescription": "$(PRODUCT_NAME) would like to access speech recognition to transcribe your voice."
25+
}
26+
]
27+
],
28+
"name": "ExpoChatGPTSample",
29+
"slug": "ExpoChatGPTSample",
30+
"android": {
31+
"permissions": ["android.permission.RECORD_AUDIO"],
32+
"package": "io.getstream.expochatgptsample"
33+
}
34+
}
35+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Drawer } from 'expo-router/drawer';
2+
import React from 'react';
3+
import { StyleSheet } from 'react-native';
4+
import { Chat, OverlayProvider, useCreateChatClient } from 'stream-chat-expo';
5+
import {
6+
chatApiKey,
7+
chatUserId,
8+
chatUserName,
9+
chatUserToken,
10+
} from '../chatConfig';
11+
import { SafeAreaProvider } from 'react-native-safe-area-context';
12+
import { AppProvider } from '../contexts/AppContext';
13+
import { StreamTheme } from '@stream-io/chat-react-native-ai';
14+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
15+
import type { LocalMessage } from 'stream-chat';
16+
import { MenuDrawer } from '../screens/MenuDrawer';
17+
18+
const isMessageAIGenerated = (message: LocalMessage) => !!message.ai_generated;
19+
20+
export default function Layout() {
21+
const chatClient = useCreateChatClient({
22+
apiKey: chatApiKey,
23+
tokenOrProvider: chatUserToken,
24+
userData: { id: chatUserId, name: chatUserName },
25+
});
26+
27+
if (!chatClient) {
28+
return null;
29+
}
30+
return (
31+
<SafeAreaProvider>
32+
<AppProvider client={chatClient}>
33+
<StreamTheme>
34+
<GestureHandlerRootView style={styles.container}>
35+
<OverlayProvider>
36+
<Chat
37+
client={chatClient}
38+
isMessageAIGenerated={isMessageAIGenerated}
39+
>
40+
<Drawer
41+
drawerContent={MenuDrawer}
42+
screenOptions={{ drawerStyle: { width: 300 } }}
43+
>
44+
<Drawer.Screen name={'index'} />
45+
</Drawer>
46+
</Chat>
47+
</OverlayProvider>
48+
</GestureHandlerRootView>
49+
</StreamTheme>
50+
</AppProvider>
51+
</SafeAreaProvider>
52+
);
53+
}
54+
55+
const styles = StyleSheet.create({
56+
container: { flex: 1 },
57+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ChatContent } from '../screens/ChatContent';
2+
3+
const Page = () => {
4+
return <ChatContent />;
5+
};
6+
export default Page;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
return {
4+
presets: ['babel-preset-expo'],
5+
plugins: [
6+
'@babel/plugin-proposal-export-namespace-from',
7+
'react-native-worklets/plugin',
8+
],
9+
};
10+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Alert } from 'react-native';
2+
import {
3+
Copy,
4+
DownloadArrow,
5+
DownloadCloud,
6+
Flag,
7+
Edit,
8+
} from 'stream-chat-expo';
9+
10+
export const bottomSheetOptions = [
11+
{
12+
title: 'Create Image',
13+
subtitle: 'Visualize anything',
14+
action: () => Alert.alert('Pressed on Create Image !'),
15+
Icon: DownloadArrow,
16+
},
17+
{
18+
title: 'Thinking',
19+
subtitle: 'Think longer for better answers',
20+
action: () => Alert.alert('Pressed on Thinking !'),
21+
Icon: Flag,
22+
},
23+
{
24+
title: 'Deep research',
25+
subtitle: 'Get a detailed report',
26+
action: () => Alert.alert('Pressed on Deep research !'),
27+
Icon: DownloadCloud,
28+
},
29+
{
30+
title: 'Web search',
31+
subtitle: 'Find real-time news and info',
32+
action: () => Alert.alert('Pressed on Web search !'),
33+
Icon: Copy,
34+
},
35+
{
36+
title: 'Study and learn',
37+
subtitle: 'Learn a new concept',
38+
action: () => Alert.alert('Pressed on Study and learn !'),
39+
Icon: Edit,
40+
},
41+
];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const chatApiKey = 'zcgvnykxsfm8';
2+
export const chatUserId = 'rn-ai-test-user-3';
3+
export const chatUserToken =
4+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm4tYWktdGVzdC11c2VyLTMifQ.kOg3zLkzHc-b_5IyckQr-RixbebN-GDs7N3c570oA5A';
5+
export const chatUserName = 'Bob';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Skia is embedded in the native binary on native platforms.
2+
export function AsyncSkia({}) {
3+
return null;
4+
}

0 commit comments

Comments
 (0)