trying to figure out jest issue.
This commit is contained in:
parent
0876db2dea
commit
889d898062
215
README.md
215
README.md
@ -1,50 +1,195 @@
|
|||||||
# Welcome to your Expo app 👋
|
# react-native-linear-gradient
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
A `<LinearGradient>` element for React Native
|
||||||
|
|
||||||
## Get started
|
[![ci][1]][2]
|
||||||
|
[![npm version][3]][4]
|
||||||
|
[![npm downloads][5]][4]
|
||||||
|
|
||||||
1. Install dependencies
|
<p align="center">
|
||||||
|
<img src="https://github.com/react-native-linear-gradient/react-native-linear-gradient/assets/743291/8ff2a78b-f0b1-463a-aa5b-555df2e71360" width="300"> <img src="https://github.com/react-native-linear-gradient/react-native-linear-gradient/assets/743291/9c738be3-6fba-43d5-9c9f-1db1c10fd377" width="300">
|
||||||
|
</p>
|
||||||
|
|
||||||
```bash
|
## Table of Contents
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the app
|
- [Installation](#installation)
|
||||||
|
- [Usage and Examples](#examples)
|
||||||
|
- [Props](#props)
|
||||||
|
- [Example App](#an-example-app)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Other Platforms](#other-platforms)
|
||||||
|
|
||||||
```bash
|
## Installation
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
```sh
|
||||||
|
yarn add react-native-linear-gradient
|
||||||
- [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.
|
Or, using npm: `npm install react-native-linear-gradient`
|
||||||
|
|
||||||
## Learn more
|
## Examples
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
[react-native-login](https://github.com/brentvatne/react-native-login) is a
|
||||||
|
legacy component which showcases the use of `<LinearGradient>`.
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
### Simple
|
||||||
- [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
|
The following code will produce something like this:
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|

|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
```javascript
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
|
|
||||||
|
// Within your render function
|
||||||
|
<LinearGradient colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
// Later on in your styles..
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
linearGradient: {
|
||||||
|
flex: 1,
|
||||||
|
paddingLeft: 15,
|
||||||
|
paddingRight: 15,
|
||||||
|
borderRadius: 5
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Gill Sans',
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 10,
|
||||||
|
color: '#ffffff',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Horizontal gradient
|
||||||
|
|
||||||
|
Using the styles from above, set `start` and `end` like this to make the gradient go from left to right, instead of from top to bottom:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}} colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text gradient (iOS)
|
||||||
|
|
||||||
|
On iOS you can use the `MaskedViewIOS` to display text with a gradient. The trick here is to render the text twice; once for the mask, and once to let the gradient have the correct size (hence the `opacity: 0`):
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<MaskedViewIOS maskElement={<Text style={styles.text} />}>
|
||||||
|
<LinearGradient colors={['#f00', '#0f0']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}>
|
||||||
|
<Text style={[styles.text, { opacity: 0 }]} />
|
||||||
|
</LinearGradient>
|
||||||
|
</MaskedViewIOS>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animated Gradient
|
||||||
|
|
||||||
|
Check out the [example app](https://github.com/react-native-linear-gradient/react-native-linear-gradient/tree/HEAD/example/) (`git clone` this project, cd into it, npm install, open in Xcode and run) to see how this is done:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*This gif was created using [licecap](http://www.cockos.com/licecap/) - a great piece of free OSS*
|
||||||
|
|
||||||
|
### Transparent Gradient
|
||||||
|
|
||||||
|
The use of `transparent` color will most likely not lead to the expected result. `transparent` is actually a transparent black color (`rgba(0, 0, 0, 0)`). If you need a gradient in which the color is "fading", you need to have the same color with changing alpha channel. Example:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// RGBA
|
||||||
|
|
||||||
|
<LinearGradient colors={['rgba(255, 255, 255, 0)', 'rgba(255, 255, 255, 1)']} {...otherGradientProps} />
|
||||||
|
|
||||||
|
// Hex
|
||||||
|
|
||||||
|
<LinearGradient colors={['#FFFFFF00', '#FFFFFF']} {...otherGradientProps} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
In addition to regular `View` props, you can also provide additional props to customize your gradient look:
|
||||||
|
|
||||||
|
#### colors
|
||||||
|
|
||||||
|
An array of at least two color values that represent gradient colors. Example: `['red', 'blue']` sets gradient from red to blue.
|
||||||
|
|
||||||
|
#### start
|
||||||
|
|
||||||
|
An optional object of the following type: `{ x: number, y: number }`. Coordinates declare the position that the gradient starts at, as a fraction of the overall size of the gradient, starting from the top left corner. Example: `{ x: 0.1, y: 0.1 }` means that the gradient will start 10% from the top and 10% from the left.
|
||||||
|
|
||||||
|
#### end
|
||||||
|
|
||||||
|
Same as start, but for the end of the gradient.
|
||||||
|
|
||||||
|
#### locations
|
||||||
|
|
||||||
|
An optional array of numbers defining the location of each gradient color stop, mapping to the color with the same index in `colors` prop. Example: `[0.1, 0.75, 1]` means that first color will take 0% - 10%, second color will take 10% - 75% and finally third color will occupy 75% - 100%.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<LinearGradient
|
||||||
|
start={{x: 0.0, y: 0.25}} end={{x: 0.5, y: 1.0}}
|
||||||
|
locations={[0,0.5,0.6]}
|
||||||
|
colors={['#4c669f', '#3b5998', '#192f6a']}
|
||||||
|
style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### useAngle / angle / angleCenter
|
||||||
|
|
||||||
|
You may want to achieve an angled gradient effect, similar to those in image editors like Photoshop.
|
||||||
|
One issue is that you have to calculate the angle based on the view's size, which only happens asynchronously and will cause unwanted flickr.
|
||||||
|
|
||||||
|
In order to do that correctly you can set `useAngle={true} angle={45} angleCenter={{x:0.5,y:0.5}}`, to achieve a gradient with a 45 degrees angle, with its center positioned in the view's exact center.
|
||||||
|
|
||||||
|
`useAngle` is used to turn on/off angle based calculation (as opposed to `start`/`end`).
|
||||||
|
`angle` is the angle in degrees.
|
||||||
|
`angleCenter` is the center point of the angle (will control the weight and stretch of the gradient like it does in photoshop.
|
||||||
|
|
||||||
|
## An example app
|
||||||
|
|
||||||
|
You can see this component in action in [brentvatne/react-native-login](https://github.com/brentvatne/react-native-login/blob/HEAD/App/Screens/LoginScreen.js#L58-L62).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### iOS build fails: library not found, "BVLinearGradient" was not found in the UIManager
|
||||||
|
|
||||||
|
1. Ensure to run `pod install` before running the app on iOS
|
||||||
|
2. Ensure you use `ios/**.xcworkspace` file instead of `ios./**.xcodeproj`
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
Clearing build caches and reinstalling dependencies sometimes solve some issues. Try next steps:
|
||||||
|
|
||||||
|
1. Reinstalling `node_modules` with `rm -rf node_modules && yarn`
|
||||||
|
2. Clearing Android Gradle cache with `(cd android && ./gradlew clean)`
|
||||||
|
3. Reinstalling iOS CocoaPods with `(cd ios && rm -rf ./ios/Pods/**) && npx pod-install`
|
||||||
|
4. Clearing Xcode Build cache (open Xcode and go to Product -> Clean Build Folder)
|
||||||
|
|
||||||
|
For other troubleshooting issues, go to [React Native Troubleshooting](https://reactnative.dev/docs/troubleshooting.html)
|
||||||
|
|
||||||
|
## Other platforms
|
||||||
|
|
||||||
|
- Web: [react-native-web-community/react-native-web-linear-gradient](https://github.com/react-native-web-community/react-native-web-linear-gradient)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[1]: https://github.com/react-native-linear-gradient/react-native-linear-gradient/workflows/ci/badge.svg
|
||||||
|
[2]: https://github.com/react-native-linear-gradient/react-native-linear-gradient/actions
|
||||||
|
[3]: https://img.shields.io/npm/v/react-native-linear-gradient.svg
|
||||||
|
[4]: https://www.npmjs.com/package/react-native-linear-gradient
|
||||||
|
[5]: https://img.shields.io/npm/dm/react-native-linear-gradient.svg
|
||||||
|
195
README.react-native-linear-gradient.md
Normal file
195
README.react-native-linear-gradient.md
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# react-native-linear-gradient
|
||||||
|
|
||||||
|
A `<LinearGradient>` element for React Native
|
||||||
|
|
||||||
|
[![ci][1]][2]
|
||||||
|
[![npm version][3]][4]
|
||||||
|
[![npm downloads][5]][4]
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://github.com/react-native-linear-gradient/react-native-linear-gradient/assets/743291/8ff2a78b-f0b1-463a-aa5b-555df2e71360" width="300"> <img src="https://github.com/react-native-linear-gradient/react-native-linear-gradient/assets/743291/9c738be3-6fba-43d5-9c9f-1db1c10fd377" width="300">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage and Examples](#examples)
|
||||||
|
- [Props](#props)
|
||||||
|
- [Example App](#an-example-app)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Other Platforms](#other-platforms)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add react-native-linear-gradient
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, using npm: `npm install react-native-linear-gradient`
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
[react-native-login](https://github.com/brentvatne/react-native-login) is a
|
||||||
|
legacy component which showcases the use of `<LinearGradient>`.
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
The following code will produce something like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
|
|
||||||
|
// Within your render function
|
||||||
|
<LinearGradient colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
// Later on in your styles..
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
linearGradient: {
|
||||||
|
flex: 1,
|
||||||
|
paddingLeft: 15,
|
||||||
|
paddingRight: 15,
|
||||||
|
borderRadius: 5
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Gill Sans',
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 10,
|
||||||
|
color: '#ffffff',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Horizontal gradient
|
||||||
|
|
||||||
|
Using the styles from above, set `start` and `end` like this to make the gradient go from left to right, instead of from top to bottom:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}} colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text gradient (iOS)
|
||||||
|
|
||||||
|
On iOS you can use the `MaskedViewIOS` to display text with a gradient. The trick here is to render the text twice; once for the mask, and once to let the gradient have the correct size (hence the `opacity: 0`):
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<MaskedViewIOS maskElement={<Text style={styles.text} />}>
|
||||||
|
<LinearGradient colors={['#f00', '#0f0']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}>
|
||||||
|
<Text style={[styles.text, { opacity: 0 }]} />
|
||||||
|
</LinearGradient>
|
||||||
|
</MaskedViewIOS>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animated Gradient
|
||||||
|
|
||||||
|
Check out the [example app](https://github.com/react-native-linear-gradient/react-native-linear-gradient/tree/HEAD/example/) (`git clone` this project, cd into it, npm install, open in Xcode and run) to see how this is done:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*This gif was created using [licecap](http://www.cockos.com/licecap/) - a great piece of free OSS*
|
||||||
|
|
||||||
|
### Transparent Gradient
|
||||||
|
|
||||||
|
The use of `transparent` color will most likely not lead to the expected result. `transparent` is actually a transparent black color (`rgba(0, 0, 0, 0)`). If you need a gradient in which the color is "fading", you need to have the same color with changing alpha channel. Example:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// RGBA
|
||||||
|
|
||||||
|
<LinearGradient colors={['rgba(255, 255, 255, 0)', 'rgba(255, 255, 255, 1)']} {...otherGradientProps} />
|
||||||
|
|
||||||
|
// Hex
|
||||||
|
|
||||||
|
<LinearGradient colors={['#FFFFFF00', '#FFFFFF']} {...otherGradientProps} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
In addition to regular `View` props, you can also provide additional props to customize your gradient look:
|
||||||
|
|
||||||
|
#### colors
|
||||||
|
|
||||||
|
An array of at least two color values that represent gradient colors. Example: `['red', 'blue']` sets gradient from red to blue.
|
||||||
|
|
||||||
|
#### start
|
||||||
|
|
||||||
|
An optional object of the following type: `{ x: number, y: number }`. Coordinates declare the position that the gradient starts at, as a fraction of the overall size of the gradient, starting from the top left corner. Example: `{ x: 0.1, y: 0.1 }` means that the gradient will start 10% from the top and 10% from the left.
|
||||||
|
|
||||||
|
#### end
|
||||||
|
|
||||||
|
Same as start, but for the end of the gradient.
|
||||||
|
|
||||||
|
#### locations
|
||||||
|
|
||||||
|
An optional array of numbers defining the location of each gradient color stop, mapping to the color with the same index in `colors` prop. Example: `[0.1, 0.75, 1]` means that first color will take 0% - 10%, second color will take 10% - 75% and finally third color will occupy 75% - 100%.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<LinearGradient
|
||||||
|
start={{x: 0.0, y: 0.25}} end={{x: 0.5, y: 1.0}}
|
||||||
|
locations={[0,0.5,0.6]}
|
||||||
|
colors={['#4c669f', '#3b5998', '#192f6a']}
|
||||||
|
style={styles.linearGradient}>
|
||||||
|
<Text style={styles.buttonText}>
|
||||||
|
Sign in with Facebook
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### useAngle / angle / angleCenter
|
||||||
|
|
||||||
|
You may want to achieve an angled gradient effect, similar to those in image editors like Photoshop.
|
||||||
|
One issue is that you have to calculate the angle based on the view's size, which only happens asynchronously and will cause unwanted flickr.
|
||||||
|
|
||||||
|
In order to do that correctly you can set `useAngle={true} angle={45} angleCenter={{x:0.5,y:0.5}}`, to achieve a gradient with a 45 degrees angle, with its center positioned in the view's exact center.
|
||||||
|
|
||||||
|
`useAngle` is used to turn on/off angle based calculation (as opposed to `start`/`end`).
|
||||||
|
`angle` is the angle in degrees.
|
||||||
|
`angleCenter` is the center point of the angle (will control the weight and stretch of the gradient like it does in photoshop.
|
||||||
|
|
||||||
|
## An example app
|
||||||
|
|
||||||
|
You can see this component in action in [brentvatne/react-native-login](https://github.com/brentvatne/react-native-login/blob/HEAD/App/Screens/LoginScreen.js#L58-L62).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### iOS build fails: library not found, "BVLinearGradient" was not found in the UIManager
|
||||||
|
|
||||||
|
1. Ensure to run `pod install` before running the app on iOS
|
||||||
|
2. Ensure you use `ios/**.xcworkspace` file instead of `ios./**.xcodeproj`
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
Clearing build caches and reinstalling dependencies sometimes solve some issues. Try next steps:
|
||||||
|
|
||||||
|
1. Reinstalling `node_modules` with `rm -rf node_modules && yarn`
|
||||||
|
2. Clearing Android Gradle cache with `(cd android && ./gradlew clean)`
|
||||||
|
3. Reinstalling iOS CocoaPods with `(cd ios && rm -rf ./ios/Pods/**) && npx pod-install`
|
||||||
|
4. Clearing Xcode Build cache (open Xcode and go to Product -> Clean Build Folder)
|
||||||
|
|
||||||
|
For other troubleshooting issues, go to [React Native Troubleshooting](https://reactnative.dev/docs/troubleshooting.html)
|
||||||
|
|
||||||
|
## Other platforms
|
||||||
|
|
||||||
|
- Web: [react-native-web-community/react-native-web-linear-gradient](https://github.com/react-native-web-community/react-native-web-linear-gradient)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[1]: https://github.com/react-native-linear-gradient/react-native-linear-gradient/workflows/ci/badge.svg
|
||||||
|
[2]: https://github.com/react-native-linear-gradient/react-native-linear-gradient/actions
|
||||||
|
[3]: https://img.shields.io/npm/v/react-native-linear-gradient.svg
|
||||||
|
[4]: https://www.npmjs.com/package/react-native-linear-gradient
|
||||||
|
[5]: https://img.shields.io/npm/dm/react-native-linear-gradient.svg
|
1
__mocks__/jestSetup.ts
Normal file
1
__mocks__/jestSetup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
|
@ -4,14 +4,24 @@ import { Text } from 'react-native';
|
|||||||
import { Image, StyleSheet, Platform } from 'react-native';
|
import { Image, StyleSheet, Platform } from 'react-native';
|
||||||
import { Conversation, Speaker } from '../lib/conversation';
|
import { Conversation, Speaker } from '../lib/conversation';
|
||||||
import { language_matrix_entry, Translator } from '../i18n/api';
|
import { language_matrix_entry, Translator } from '../i18n/api';
|
||||||
|
import ConversationThread from '@/components/ConversationThread';
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
|
|
||||||
const [language, setLanguage] = useState<language_matrix_entry | undefined>()
|
const [language, setLanguage] = useState<language_matrix_entry | undefined>()
|
||||||
|
const [conversation, setConversation] = useState<Conversation|undefined>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
language ? <ConversationPlay language={language} /> :
|
conversation ? <ConversationThread conversation={conversation} /> :
|
||||||
<LanguageSelection onLanguageSelected={(l) => setLanguage(l)} />
|
<LanguageSelection onLanguageSelected={
|
||||||
|
(language) => {
|
||||||
|
setConversation(new Conversation(
|
||||||
|
new Translator("en", language.code),
|
||||||
|
{id: "host", language: "en"},
|
||||||
|
{id: "guest", language: language.code},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,21 +9,21 @@ describe('Conversation', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const translator = new Translator("en", "es");
|
const translator = new Translator("en", "es");
|
||||||
const s1: Speaker = { language: "en", id: "s1" };
|
const s1: Speaker = { language: "en", id: "host" };
|
||||||
const s2: Speaker = { id: "s2", language: "es" }
|
const s2: Speaker = { id: "guest", language: "es" }
|
||||||
conversation = new Conversation(translator, s1, s2);
|
conversation = new Conversation(translator, s1, s2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a message to the conversation', () => {
|
it('should add a message to the conversation', () => {
|
||||||
conversation.addMessage({ id: "s1", language: "en" }, "Hello");
|
conversation.addMessage({ id: "host", language: "en" }, "Hello");
|
||||||
expect(conversation.length).toBe(1);
|
expect(conversation.length).toBe(1);
|
||||||
expect(conversation[0].speaker.id).toEqual("s1");
|
expect(conversation[0].speaker.id).toEqual("host");
|
||||||
expect(conversation[0].text).toEqual("Hello");
|
expect(conversation[0].text).toEqual("Hello");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should translate the last message in the conversation', async () => {
|
it('should translate the last message in the conversation', async () => {
|
||||||
|
|
||||||
conversation.addMessage({ id: "s1", language: "en" }, "Hello");
|
conversation.addMessage({ id: "host", language: "en" }, "Hello");
|
||||||
|
|
||||||
await conversation.translateLast();
|
await conversation.translateLast();
|
||||||
expect(conversation[conversation.length - 1].translation).toEqual("Hola");
|
expect(conversation[conversation.length - 1].translation).toEqual("Hola");
|
||||||
|
@ -5,33 +5,54 @@ export type Speaker = {
|
|||||||
language : string,
|
language : string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Message {
|
export class Message {
|
||||||
public translation? : string;
|
public translation? : string;
|
||||||
constructor (public text : string, public speaker : Speaker) {}
|
public onTextUpdate?: (message: Message) => void;
|
||||||
|
public onTextDone?: (message: Message) => Promise<void>;
|
||||||
|
public onTranslationDone?: (message: Message) => void;
|
||||||
|
|
||||||
|
constructor (public conversation : Conversation, public speaker : Speaker, public text? : string) {}
|
||||||
|
|
||||||
public async translate(translator : Translator, language? : string) {
|
public async translate(translator : Translator, language? : string) {
|
||||||
|
if (!this.text) throw new Error("No text")
|
||||||
this.translation = await translator.translate(this.text, language);
|
this.translation = await translator.translate(this.text, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get otherSpeaker() {
|
||||||
|
return this.speaker.id === this.conversation.host.id ? this.conversation.guest : this.conversation.host
|
||||||
|
}
|
||||||
|
|
||||||
|
get otherLanguage() {
|
||||||
|
return this.otherSpeaker.language
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Conversation extends Array<Message> {
|
export class Conversation extends Array<Message> {
|
||||||
|
|
||||||
|
public onAddMessage? : (conversation : Conversation) => Promise<any>;
|
||||||
|
public onTranslationDone? : (conversation : Conversation) => Promise<any>;
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private translator : Translator,
|
private translator : Translator,
|
||||||
public s1 : Speaker,
|
public host : Speaker,
|
||||||
public s2 : Speaker,
|
public guest : Speaker,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMessage(speaker : Speaker, text : string) {
|
public addMessage(speaker : Speaker, text? : string) {
|
||||||
this.push(new Message(text, speaker));
|
this.push(new Message(this, speaker, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async translateMessage(i : number) {
|
public async translateMessage(i : number) {
|
||||||
if (!this[i]) throw new Error(`${i} is not a valid message number`);
|
if (!this[i]) throw new Error(`${i} is not a valid message number`);
|
||||||
const otherSpeaker = this[i].speaker.id === this.s1.id ? this.s2 : this.s1
|
console.log(`Translating sentence to %s: %s`, this[i].otherLanguage, this[i].text)
|
||||||
console.log(`Translating sentence to %s: %s`, otherSpeaker.language, this[i].text)
|
await this[i].translate(this.translator, this[i].otherLanguage);
|
||||||
await this[i].translate(this.translator, otherSpeaker.language);
|
}
|
||||||
|
|
||||||
|
get lastMessage() {
|
||||||
|
return this[this.length-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
public async translateLast() {
|
public async translateLast() {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module.exports = {
|
// module.exports = {
|
||||||
presets: [
|
// presets: [
|
||||||
['@babel/preset-env', {targets: {node: 'current'}}],
|
// ['@babel/preset-env', {targets: {node: 'current'}}],
|
||||||
'@babel/preset-typescript',
|
// '@babel/preset-typescript',
|
||||||
],
|
// ],
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
export const presets = ['module:metro-react-native-babel-preset', 'module:@babel/plugin-transform-private-methods',];
|
@ -1,107 +0,0 @@
|
|||||||
|
|
||||||
interface ConversationPlayProps {
|
|
||||||
translator: Translator;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConversationPlay (props : ConversationPlayProps) {
|
|
||||||
const [language, setLanguage] = useState<string>(languages[0].code);
|
|
||||||
const [message, setMessage] = useState<string>('');
|
|
||||||
const [conversation, setConversation] = useState<Conversation | null>(null);
|
|
||||||
|
|
||||||
const startConversation = () => {
|
|
||||||
const speaker: Speaker = { id: "user", language };
|
|
||||||
const newConversation = new Conversation(translator, speaker);
|
|
||||||
setConversation(newConversation);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addMessage = async () => {
|
|
||||||
if (conversation) {
|
|
||||||
conversation.addMessage({ id: "user", language }, message);
|
|
||||||
await conversation.translateLast();
|
|
||||||
setMessage('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text>Select Language:</Text>
|
|
||||||
{languages.map(lang => (
|
|
||||||
<Button key={lang.code} title={lang.name} onPress={() => setLanguage(lang.code)} />
|
|
||||||
))}
|
|
||||||
<Button title="Start Conversation" onPress={startConversation} />
|
|
||||||
{conversation && (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Type a message"
|
|
||||||
value={message}
|
|
||||||
onChangeText={setMessage}
|
|
||||||
/>
|
|
||||||
<Button title="Send Message" onPress={addMessage} />
|
|
||||||
{conversation.messages.map((msg, index) => (
|
|
||||||
<View key={index}>
|
|
||||||
<Text>{msg.text}</Text>
|
|
||||||
{msg.translation && <Text>Translation: {msg.translation}</Text>}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConversationPlay;import React, { useState } from 'react';
|
|
||||||
import { View, Text, TextInput, Button } from 'react-native';
|
|
||||||
import { Conversation, Speaker, Translator } from './index';
|
|
||||||
import {LANG_FLAGS} from "@/app/i18n/lang"
|
|
||||||
|
|
||||||
interface ConversationPlayProps {
|
|
||||||
translator: Translator;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConversationPlay: React.FC<ConversationPlayProps> = ({ translator }) => {
|
|
||||||
const [language, setLanguage] = useState<string>(languages[0].code);
|
|
||||||
const [message, setMessage] = useState<string>('');
|
|
||||||
const [conversation, setConversation] = useState<Conversation | null>(null);
|
|
||||||
|
|
||||||
const startConversation = () => {
|
|
||||||
const speaker: Speaker = { id: "user", language };
|
|
||||||
const newConversation = new Conversation(translator, speaker);
|
|
||||||
setConversation(newConversation);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addMessage = async () => {
|
|
||||||
if (conversation) {
|
|
||||||
conversation.addMessage({ id: "user", language }, message);
|
|
||||||
await conversation.translateLast();
|
|
||||||
setMessage('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Text>Select Language:</Text>
|
|
||||||
{languages.map(lang => (
|
|
||||||
<Button key={lang.code} title={lang.name} onPress={() => setLanguage(lang.code)} />
|
|
||||||
))}
|
|
||||||
<Button title="Start Conversation" onPress={startConversation} />
|
|
||||||
{conversation && (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Type a message"
|
|
||||||
value={message}
|
|
||||||
onChangeText={setMessage}
|
|
||||||
/>
|
|
||||||
<Button title="Send Message" onPress={addMessage} />
|
|
||||||
{conversation.messages.map((msg, index) => (
|
|
||||||
<View key={index}>
|
|
||||||
<Text>{msg.text}</Text>
|
|
||||||
{msg.translation && <Text>Translation: {msg.translation}</Text>}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConversationPlay;
|
|
40
components/ConversationThread.tsx
Normal file
40
components/ConversationThread.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { Conversation } from '@/app/lib/conversation';
|
||||||
|
import MessageBubble from '@/components/ui/MessageBubble';
|
||||||
|
|
||||||
|
interface ConversationThreadProps {
|
||||||
|
conversation: Conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConversationThread: React.FC<ConversationThreadProps> = ({ conversation }) => {
|
||||||
|
const [messages, setMessages] = useState<Message[]>(conversation.messages);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateMessages = () => {
|
||||||
|
setMessages([...conversation.messages]);
|
||||||
|
};
|
||||||
|
|
||||||
|
conversation.on('messageAdded', updateMessages);
|
||||||
|
conversation.on('messageUpdated', updateMessages);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
conversation.off('messageAdded', updateMessages);
|
||||||
|
conversation.off('messageUpdated', updateMessages);
|
||||||
|
};
|
||||||
|
}, [conversation]);
|
||||||
|
|
||||||
|
const renderMessages = () => (
|
||||||
|
messages.map((message, index) => (
|
||||||
|
<MessageBubble key={index} message={message} />
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
{renderMessages()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConversationThread;
|
@ -36,8 +36,17 @@ const intelligence: Record<string, string> = {
|
|||||||
H: "High",
|
H: "High",
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is the configuration schema
|
|
||||||
const settings: SettingsElement[] = [
|
export default function Settings() {
|
||||||
|
// Simply pass the schema here
|
||||||
|
// It integrates in your existing `NavigationContainer` or `Screen`
|
||||||
|
const [translator, setTranslator] = useState<Translator>(new Translator("en"))
|
||||||
|
const [languages, setLanguages] = useState<language_matrix | undefined>();
|
||||||
|
const [languagesLoaded, setLanguagesLoaded] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
// This is the configuration schema
|
||||||
|
const settings: SettingsElement[] = [
|
||||||
{
|
{
|
||||||
label: "Host Language",
|
label: "Host Language",
|
||||||
type: "enum",
|
type: "enum",
|
||||||
@ -59,14 +68,7 @@ const settings: SettingsElement[] = [
|
|||||||
get: confGet.bind(null, "@int", "M"),
|
get: confGet.bind(null, "@int", "M"),
|
||||||
set: confSet.bind(null, "@int"),
|
set: confSet.bind(null, "@int"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Settings() {
|
|
||||||
// Simply pass the schema here
|
|
||||||
// It integrates in your existing `NavigationContainer` or `Screen`
|
|
||||||
const [translator, setTranslator] = useState<Translator>(new Translator("en"))
|
|
||||||
const [languages, setLanguages] = useState<language_matrix | undefined>();
|
|
||||||
const [languagesLoaded, setLanguagesLoaded] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
64
components/ui/MessageBubble.tsx
Normal file
64
components/ui/MessageBubble.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Translator } from "@/app/i18n/api";
|
||||||
|
import { Message, Speaker } from "@/app/lib/conversation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { StyleSheet, Text } from "react-native";
|
||||||
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
|
||||||
|
type MessageProps = {
|
||||||
|
message: Message;
|
||||||
|
translator: Translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageBubble = (props: MessageProps) => {
|
||||||
|
const [text, setText] = useState(props.message.text);
|
||||||
|
const [translatedText, setTranslatedText] = useState(props.message.text);
|
||||||
|
const [isTranslating, setIsTranslating] = useState<boolean>(false);
|
||||||
|
|
||||||
|
props.message.onTextUpdate = (message: Message) => {
|
||||||
|
setText(message.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.message.onTextDone = async (message: Message) => {
|
||||||
|
setIsTranslating(true);
|
||||||
|
await props.message.translate(props.translator)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.message.onTranslationDone = (message: Message) => {
|
||||||
|
if (!message.translation) throw new Error("Missing translation");
|
||||||
|
setTranslatedText(message.translation);
|
||||||
|
setIsTranslating(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const spId = props.message.speaker.id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView>
|
||||||
|
{text && (
|
||||||
|
<Text>{text}</Text>
|
||||||
|
)}
|
||||||
|
{translatedText &&
|
||||||
|
<Text>{translatedText}</Text>
|
||||||
|
}
|
||||||
|
</SafeAreaView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// const bubbleStyle = StyleSheet.create({
|
||||||
|
// host: {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// guest: {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const textStyles = StyleSheet.create({
|
||||||
|
// native: {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// translation: {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
export default MessageBubble;
|
51
components/ui/__tests__/Message.spec.tsx
Normal file
51
components/ui/__tests__/Message.spec.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { act } from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react-native'
|
||||||
|
import MessageBubble from '@/components/ui/MessageBubble';
|
||||||
|
import { Conversation, Speaker } from '@/app/lib/conversation';
|
||||||
|
import {Translator} from '@/app/i18n/api';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
describe('Message Component', () => {
|
||||||
|
const translator = new Translator('en', 'es');
|
||||||
|
|
||||||
|
const host : Speaker = {id : "host", language : "en"}
|
||||||
|
const guest : Speaker = {id : "guest", language: "es"}
|
||||||
|
|
||||||
|
let conversation : Conversation;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
conversation = new Conversation(translator, host, guest);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the message text correctly', async () => {
|
||||||
|
conversation.addMessage(host, "Hello, World!");
|
||||||
|
const message = conversation[0];
|
||||||
|
render(<View></View>);
|
||||||
|
// render(<MessageBubble message={message} />);
|
||||||
|
expect(await screen.findByText(message.text as string)).toBeOnTheScreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translates the message when target language is provided', async () => {
|
||||||
|
const translatedText = 'Hola, ¿cómo estás?';
|
||||||
|
conversation.addMessage(host, "Hello, how are you?");
|
||||||
|
await conversation.translateLast();
|
||||||
|
|
||||||
|
render(<MessageBubble message={conversation[0]} translator={translator} />);
|
||||||
|
expect(await screen.findByText(translatedText)).toBeOnTheScreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('widget still renders pre-translation', async () => {
|
||||||
|
const text = "Hello, how are you?"
|
||||||
|
const translatedText = 'Hola, ¿cómo estás?';
|
||||||
|
conversation.addMessage(host, text);
|
||||||
|
render(<MessageBubble message={conversation[0]} translator={translator} />);
|
||||||
|
expect(await screen.findByText(text)).toBeOnTheScreen();
|
||||||
|
expect(await screen.findByText(translatedText)).not.toBeOnTheScreen();
|
||||||
|
await act(async () => {
|
||||||
|
await conversation.translateLast();
|
||||||
|
});
|
||||||
|
expect(await screen.findByText(text)).toBeOnTheScreen();
|
||||||
|
expect(await screen.findByText(translatedText)).toBeOnTheScreen();
|
||||||
|
});
|
||||||
|
});
|
@ -1,17 +1,22 @@
|
|||||||
import type { Config } from '@jest/types';
|
// jest.config.ts
|
||||||
import {resolve} from "path"
|
import { resolve } from "path"
|
||||||
// Sync object
|
import { createJsWithBabelPreset, JestConfigWithTsJest } from 'ts-jest'
|
||||||
const config: Config.InitialOptions = {
|
|
||||||
verbose: true,
|
|
||||||
transform: {
|
|
||||||
"^.+.tsx?$": ["ts-jest", {}],
|
|
||||||
},
|
|
||||||
testEnvironment: "node",
|
|
||||||
// runner: "jest-runner-tsc",
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^@/(.*)$': "<rootDir>/$1"
|
|
||||||
},
|
|
||||||
resolver: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
const jsWithBabelPreset = createJsWithBabelPreset({
|
||||||
|
tsconfig: 'tsconfig.spec.json',
|
||||||
|
babelConfig: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const jestConfig: JestConfigWithTsJest = {
|
||||||
|
preset: 'react-native',
|
||||||
|
transform: jsWithBabelPreset.transform,
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||||
|
// moduleNameMapper: {
|
||||||
|
// '^@/(.*)$': resolve("<rootDir>", "$1")
|
||||||
|
// },
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'<rootDir>/node_modules/(react-clone-referenced-element|@react-native-community|react-navigation|@react-navigation/.*|@unimodules/.*|native-base|react-native-code-push)',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default jestConfig
|
19
package.json
19
package.json
@ -15,19 +15,19 @@
|
|||||||
"@babel/runtime": "^7.26.7",
|
"@babel/runtime": "^7.26.7",
|
||||||
"@expo/vector-icons": "^14.0.4",
|
"@expo/vector-icons": "^14.0.4",
|
||||||
"@mmomtchev/react-native-settings": "^1.1.0",
|
"@mmomtchev/react-native-settings": "^1.1.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
"@react-native-async-storage/async-storage": "^1.24.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||||
"@react-navigation/elements": "^2.2.5",
|
"@react-navigation/elements": "^2.2.5",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "^7.0.14",
|
||||||
"@react-navigation/native-stack": "^7.2.0",
|
"@react-navigation/native-stack": "^7.2.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"expo-blur": "~14.0.2",
|
"expo-blur": "~14.0.3",
|
||||||
"expo-constants": "~17.0.4",
|
"expo-constants": "~17.0.5",
|
||||||
"expo-doctor": "^1.12.5",
|
"expo-doctor": "^1.12.5",
|
||||||
"expo-font": "~13.0.3",
|
"expo-font": "~13.0.3",
|
||||||
"expo-haptics": "~14.0.1",
|
"expo-haptics": "~14.0.1",
|
||||||
"expo-linking": "~7.0.4",
|
"expo-linking": "~7.0.5",
|
||||||
"expo-router": "~4.0.17",
|
"expo-router": "~4.0.17",
|
||||||
"expo-splash-screen": "~0.29.21",
|
"expo-splash-screen": "~0.29.21",
|
||||||
"expo-sqlite": "~15.0.6",
|
"expo-sqlite": "~15.0.6",
|
||||||
@ -44,7 +44,8 @@
|
|||||||
"react-native-cache": "^2.0.3",
|
"react-native-cache": "^2.0.3",
|
||||||
"react-native-country-flag": "^2.0.2",
|
"react-native-country-flag": "^2.0.2",
|
||||||
"react-native-gesture-handler": "~2.20.2",
|
"react-native-gesture-handler": "~2.20.2",
|
||||||
"react-native-onyx": "^2.0.91",
|
"react-native-linear-gradient": "^2.8.3",
|
||||||
|
"react-native-onyx": "^2.0.92",
|
||||||
"react-native-quick-sqlite": "^8.2.7",
|
"react-native-quick-sqlite": "^8.2.7",
|
||||||
"react-native-reanimated": "~3.16.7",
|
"react-native-reanimated": "~3.16.7",
|
||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
@ -52,23 +53,27 @@
|
|||||||
"react-native-sqlite-storage": "^6.0.1",
|
"react-native-sqlite-storage": "^6.0.1",
|
||||||
"react-native-web": "~0.19.13",
|
"react-native-web": "~0.19.13",
|
||||||
"react-native-webview": "13.12.5",
|
"react-native-webview": "13.12.5",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2",
|
||||||
|
"whisper.rn": "^0.3.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.7",
|
"@babel/core": "^7.26.7",
|
||||||
|
"@babel/plugin-transform-private-methods": "^7.25.9",
|
||||||
"@babel/preset-typescript": "^7.26.0",
|
"@babel/preset-typescript": "^7.26.0",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
|
"@testing-library/react-native": "^13.0.1",
|
||||||
"@types/cheerio": "^0.22.35",
|
"@types/cheerio": "^0.22.35",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/jquery": "^3.5.32",
|
"@types/jquery": "^3.5.32",
|
||||||
"@types/react": "~18.3.18",
|
"@types/react": "~18.3.18",
|
||||||
"@types/react-native-sqlite-storage": "^6.0.5",
|
"@types/react-native-sqlite-storage": "^6.0.5",
|
||||||
"@types/react-test-renderer": "^18.3.1",
|
"@types/react-test-renderer": "^18.3.1",
|
||||||
"expo": "~52.0.27",
|
"expo": "~52.0.28",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-expo": "~52.0.3",
|
"jest-expo": "~52.0.3",
|
||||||
"jest-runner-tsc": "^1.6.0",
|
"jest-runner-tsc": "^1.6.0",
|
||||||
|
"metro-react-native-babel-preset": "^0.77.0",
|
||||||
"react-test-renderer": "18.3.1",
|
"react-test-renderer": "18.3.1",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
11072
pnpm-lock.yaml
generated
11072
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
|
"jsx": "react",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
|
7
tsconfig.spec.json
Normal file
7
tsconfig.spec.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// tsconfig.spec.json
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-native"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user