add readme. fix downloader operation.
This commit is contained in:
parent
4549442bd8
commit
d00e6d62ff
226
README.md
226
README.md
@ -1,195 +1,81 @@
|
|||||||
# react-native-linear-gradient
|
# Translation Terrace
|
||||||
|
|
||||||
A `<LinearGradient>` element for React Native
|
Translation Terrace is an Expo/React-Native application designed for a translation kiosk. It leverages an offline Whisper speech-to-text model and interfaces with a LibreTranslate server to provide seamless translation services.
|
||||||
|
|
||||||
[![ci][1]][2]
|
## Project Structure
|
||||||
[![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">
|
translation-terraces/
|
||||||
</p>
|
├── expo-file-system/
|
||||||
|
│ └── next.js
|
||||||
## Table of Contents
|
├── @react-native-async-storage/
|
||||||
|
│ └── async-storage.ts
|
||||||
- [Installation](#installation)
|
├── .ollama/
|
||||||
- [Usage and Examples](#examples)
|
│ ├── ExampleComponent.tsx
|
||||||
- [Props](#props)
|
│ └── ExampleTest.spec.tsx
|
||||||
- [Example App](#an-example-app)
|
├── package.json
|
||||||
- [Troubleshooting](#troubleshooting)
|
├── README.md
|
||||||
- [Other Platforms](#other-platforms)
|
└── ...
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn add react-native-linear-gradient
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, using npm: `npm install react-native-linear-gradient`
|
## Technologies Used
|
||||||
|
|
||||||
## Examples
|
- **React-Native**: Core framework for building the application.
|
||||||
|
- **Whisper Speech-to-Text Model**: Offline model for converting spoken language to text.
|
||||||
|
- **LibreTranslate Server**: Interface for translation services.
|
||||||
|
|
||||||
[react-native-login](https://github.com/brentvatne/react-native-login) is a
|
## Installation Instructions
|
||||||
legacy component which showcases the use of `<LinearGradient>`.
|
|
||||||
|
|
||||||
### Simple
|
1. **Install Packages**: Run `npm install` to install all necessary dependencies as listed in `package.json`.
|
||||||
|
2. **Prebuild Android**: Since this project cannot be run using Expo Go, prebuild the Android app by running `npm run android`.
|
||||||
|
3. **Run Unit Tests**: Execute Jest unit tests with `npm run test`.
|
||||||
|
|
||||||
The following code will produce something like this:
|
## Usage Examples
|
||||||
|
|
||||||

|
### Example 1: Initializing Whisper Model
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import { initializeWhisper } from './path/to/whisper';
|
||||||
|
|
||||||
// Within your render function
|
const initModel = async () => {
|
||||||
<LinearGradient colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
try {
|
||||||
<Text style={styles.buttonText}>
|
await initializeWhisper();
|
||||||
Sign in with Facebook
|
console.log('Whisper model initialized successfully.');
|
||||||
</Text>
|
} catch (error) {
|
||||||
</LinearGradient>
|
console.error('Error initializing Whisper model:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Later on in your styles..
|
initModel();
|
||||||
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
|
### Example 2: Translating Text
|
||||||
|
|
||||||
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
|
```javascript
|
||||||
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}} colors={['#4c669f', '#3b5998', '#192f6a']} style={styles.linearGradient}>
|
import { translateText } from './path/to/libreTranslate';
|
||||||
<Text style={styles.buttonText}>
|
|
||||||
Sign in with Facebook
|
const translate = async () => {
|
||||||
</Text>
|
try {
|
||||||
</LinearGradient>
|
const translatedText = await translateText('Hello, world!', 'en', 'es');
|
||||||
|
console.log('Translated text:', translatedText);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error translating text:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
translate();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Text gradient (iOS)
|
## Contributing Guidelines
|
||||||
|
|
||||||
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`):
|
1. **Create Feature Branch**: Use the naming convention `<year>-<work week>_<username>_<feature_description>` for your feature branch.
|
||||||
|
2. **Initiate Pull Request (PR)**: Open a PR and provide a detailed description of the changes made.
|
||||||
|
3. **Discuss Features**: Suggest new features or improvements in GitTea issues.
|
||||||
|
|
||||||
```jsx
|
## License Information
|
||||||
<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
|
This project is licensed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file.
|
||||||
|
|
||||||
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:
|
---
|
||||||
|
|
||||||

|
Feel free to explore the codebase and contribute to Translation Terrace! If you have any questions or need further assistance, feel free to reach out.
|
||||||
|
|
||||||
*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
|
|
@ -17,7 +17,7 @@
|
|||||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="landscape">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
@ -3,7 +3,7 @@ import * as FileSystem from "expo-file-system";
|
|||||||
import { File, Paths } from 'expo-file-system/next';
|
import { File, Paths } from 'expo-file-system/next';
|
||||||
import { getDb } from "./db";
|
import { getDb } from "./db";
|
||||||
|
|
||||||
export const WHISPER_MODEL_PATH = Paths.join(FileSystem.bundleDirectory || "file:///", "whisper");
|
export const WHISPER_MODEL_PATH = Paths.join(FileSystem.documentDirectory || "file:///", "whisper");
|
||||||
export const WHISPER_MODEL_DIR = new File(WHISPER_MODEL_PATH);
|
export const WHISPER_MODEL_DIR = new File(WHISPER_MODEL_PATH);
|
||||||
|
|
||||||
// Thanks to https://medium.com/@fabi.mofar/downloading-and-saving-files-in-react-native-expo-5b3499adda84
|
// Thanks to https://medium.com/@fabi.mofar/downloading-and-saving-files-in-react-native-expo-5b3499adda84
|
||||||
@ -51,19 +51,19 @@ export type whisper_model_tag_t = (typeof WHISPER_MODEL_TAGS)[number];
|
|||||||
export const WHISPER_MODELS = {
|
export const WHISPER_MODELS = {
|
||||||
small: {
|
small: {
|
||||||
source:
|
source:
|
||||||
"https://huggingface.co/openai/whisper-small/blob/main/pytorch_model.bin",
|
"https://huggingface.co/openai/whisper-small/blob/resolve/pytorch_model.bin",
|
||||||
target: "small.bin",
|
target: "small.bin",
|
||||||
label: "Small",
|
label: "Small",
|
||||||
},
|
},
|
||||||
medium: {
|
medium: {
|
||||||
source:
|
source:
|
||||||
"https://huggingface.co/openai/whisper-medium/blob/main/pytorch_model.bin",
|
"https://huggingface.co/openai/whisper-medium/resolve/main/pytorch_model.bin",
|
||||||
target: "medium.bin",
|
target: "medium.bin",
|
||||||
label: "Medium",
|
label: "Medium",
|
||||||
},
|
},
|
||||||
large: {
|
large: {
|
||||||
source:
|
source:
|
||||||
"https://huggingface.co/openai/whisper-large/blob/main/pytorch_model.bin",
|
"https://huggingface.co/openai/whisper-large/resolve/main/pytorch_model.bin",
|
||||||
target: "large.bin",
|
target: "large.bin",
|
||||||
label: "Large",
|
label: "Large",
|
||||||
},
|
},
|
||||||
@ -167,12 +167,9 @@ export async function initiateWhisperDownload(
|
|||||||
|
|
||||||
console.debug("Starting download of %s", whisper_model);
|
console.debug("Starting download of %s", whisper_model);
|
||||||
|
|
||||||
if (!WHISPER_MODEL_DIR.exists) {
|
|
||||||
await FileSystem.makeDirectoryAsync(WHISPER_MODEL_PATH, {
|
await FileSystem.makeDirectoryAsync(WHISPER_MODEL_PATH, {
|
||||||
intermediates: true,
|
intermediates: true,
|
||||||
});
|
});
|
||||||
console.debug("Created %s", WHISPER_MODEL_DIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whisperTarget = getWhisperTarget(whisper_model);
|
const whisperTarget = getWhisperTarget(whisper_model);
|
||||||
|
|
||||||
@ -197,7 +194,14 @@ export async function initiateWhisperDownload(
|
|||||||
const resumable = FileSystem.createDownloadResumable(
|
const resumable = FileSystem.createDownloadResumable(
|
||||||
spec.source,
|
spec.source,
|
||||||
whisperTarget.uri,
|
whisperTarget.uri,
|
||||||
{},
|
{
|
||||||
|
md5: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Accept': 'application/octet-stream',
|
||||||
|
},
|
||||||
|
sessionType: FileSystem.FileSystemSessionType.BACKGROUND
|
||||||
|
},
|
||||||
// On each data write, update the whisper model download status.
|
// On each data write, update the whisper model download status.
|
||||||
// Note that since createDownloadResumable callback only works in the foreground,
|
// Note that since createDownloadResumable callback only works in the foreground,
|
||||||
// a background process will also be updating the file size.
|
// a background process will also be updating the file size.
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
getWhisperTarget,
|
getWhisperTarget,
|
||||||
whisper_model_tag_t,
|
whisper_model_tag_t,
|
||||||
} from "@/app/lib/whisper";
|
} from "@/app/lib/whisper";
|
||||||
import { Paths } from "expo-file-system/next";
|
import { File, Paths } from "expo-file-system/next";
|
||||||
|
|
||||||
type Language = {
|
type Language = {
|
||||||
code: string;
|
code: string;
|
||||||
@ -58,6 +58,7 @@ const SettingsComponent: React.FC = () => {
|
|||||||
FileSystem.DownloadProgressData | undefined
|
FileSystem.DownloadProgressData | undefined
|
||||||
>();
|
>();
|
||||||
const [downloader, setDownloader] = useState<DownloadResumable | undefined>();
|
const [downloader, setDownloader] = useState<DownloadResumable | undefined>();
|
||||||
|
const [whisperModelTarget, setWhisperModelTarget] = useState<File | undefined>()
|
||||||
|
|
||||||
const fillHostLanguageOptions = async () => {
|
const fillHostLanguageOptions = async () => {
|
||||||
const settings = await Settings.getDefault();
|
const settings = await Settings.getDefault();
|
||||||
@ -96,10 +97,11 @@ const SettingsComponent: React.FC = () => {
|
|||||||
setLibretranslateBaseUrl(libretranslateUrl);
|
setLibretranslateBaseUrl(libretranslateUrl);
|
||||||
console.log("libretranslate url = %s", libretranslateUrl);
|
console.log("libretranslate url = %s", libretranslateUrl);
|
||||||
try {
|
try {
|
||||||
const wModel = await settings.getWhisperModel();
|
const wModel = await settings.getWhisperModel() || "small";
|
||||||
setWhisperModel(wModel || "small");
|
setWhisperModel(wModel);
|
||||||
|
setWhisperModelTarget(new File(WHISPER_MODELS[wModel].target))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn("Could not set whisper model: %s", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// setWhisperModel(wModel);
|
// setWhisperModel(wModel);
|
||||||
@ -112,7 +114,9 @@ const SettingsComponent: React.FC = () => {
|
|||||||
if (!whisperModel) return null;
|
if (!whisperModel) return null;
|
||||||
const dlStatus = await getWhisperDownloadStatus(whisperModel);
|
const dlStatus = await getWhisperDownloadStatus(whisperModel);
|
||||||
setDownloadStatus(dlStatus);
|
setDownloadStatus(dlStatus);
|
||||||
}, 200);
|
setWhisperModelTarget(new File(WHISPER_MODELS[whisperModel].target))
|
||||||
|
console.log("Setting whisper model target to %s", whisperModelTarget);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (!libretranslateBaseUrl) return;
|
if (!libretranslateBaseUrl) return;
|
||||||
@ -150,6 +154,16 @@ const SettingsComponent: React.FC = () => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fileExists = async (file: File) => {
|
||||||
|
const info = await FileSystem.getInfoAsync(file.uri);
|
||||||
|
return info.exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doDelete = async () => {
|
||||||
|
if (!whisperModelTarget) return;
|
||||||
|
whisperModelTarget.delete();
|
||||||
|
}
|
||||||
|
|
||||||
const doReadownload = async () => {
|
const doReadownload = async () => {
|
||||||
if (!whisperModel) return;
|
if (!whisperModel) return;
|
||||||
await initiateWhisperDownload(whisperModel, {
|
await initiateWhisperDownload(whisperModel, {
|
||||||
@ -164,6 +178,7 @@ const SettingsComponent: React.FC = () => {
|
|||||||
setDownloader(
|
setDownloader(
|
||||||
await initiateWhisperDownload(whisperModel, {
|
await initiateWhisperDownload(whisperModel, {
|
||||||
onDownload: setWhisperDownloadProgress,
|
onDownload: setWhisperDownloadProgress,
|
||||||
|
force_redownload: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await downloader?.downloadAsync();
|
await downloader?.downloadAsync();
|
||||||
@ -195,6 +210,18 @@ const SettingsComponent: React.FC = () => {
|
|||||||
await fillHostLanguageOptions();
|
await fillHostLanguageOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWhisperModelChange = async (value: string) => {
|
||||||
|
const settings = await Settings.getDefault();
|
||||||
|
setWhisperModel(value);
|
||||||
|
await settings.setWhisperModel(value);
|
||||||
|
setWhisperModelTarget(getWhisperTarget(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
const doStopDownload = async () => {
|
||||||
|
if (!downloader) return;
|
||||||
|
await downloader.pauseAsync()
|
||||||
|
}
|
||||||
|
|
||||||
return isLoaded ? (
|
return isLoaded ? (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.label}>Host Language:</Text>
|
<Text style={styles.label}>Host Language:</Text>
|
||||||
@ -226,9 +253,9 @@ const SettingsComponent: React.FC = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
<Picker
|
<Picker
|
||||||
selectedValue={whisperModel || ""}
|
selectedValue={whisperModel || "small"}
|
||||||
style={{ height: 50, width: "100%" }}
|
style={{ height: 50, width: "100%" }}
|
||||||
onValueChange={setWhisperModel}
|
onValueChange={handleWhisperModelChange}
|
||||||
accessibilityHint="language"
|
accessibilityHint="language"
|
||||||
>
|
>
|
||||||
{Object.entries(WHISPER_MODELS).map(([key, { label }]) => (
|
{Object.entries(WHISPER_MODELS).map(([key, { label }]) => (
|
||||||
@ -239,13 +266,28 @@ const SettingsComponent: React.FC = () => {
|
|||||||
{ /* If there's a downloader, that means we're in the middle of a download */}
|
{ /* If there's a downloader, that means we're in the middle of a download */}
|
||||||
{downloader && whisperDownloadProgress && (
|
{downloader && whisperDownloadProgress && (
|
||||||
<Text>
|
<Text>
|
||||||
{whisperDownloadProgress.totalBytesWritten} of {whisperDownloadProgress.totalBytesExpectedToWrite}
|
{whisperDownloadProgress.totalBytesWritten} bytes of {whisperDownloadProgress.totalBytesExpectedToWrite} bytes
|
||||||
|
|
||||||
|
({Math.round((whisperDownloadProgress.totalBytesWritten / whisperDownloadProgress.totalBytesExpectedToWrite) * 100)} %)
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Pressable onPress={doDownload} style={styles.button}>
|
<View style={styles.downloadButtonWrapper}>
|
||||||
|
{downloader && whisperDownloadProgress && (whisperDownloadProgress.totalBytesWritten !== whisperDownloadProgress.totalBytesExpectedToWrite) ? (
|
||||||
|
(<Pressable onPress={doStopDownload} style={styles.pauseDownloadButton}>
|
||||||
|
<Text style={styles.buttonText}>Pause Download</Text>
|
||||||
|
</Pressable>)
|
||||||
|
) :
|
||||||
|
(<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||||
<Text style={styles.buttonText}>Download</Text>
|
<Text style={styles.buttonText}>Download</Text>
|
||||||
</Pressable>
|
</Pressable>)
|
||||||
|
}
|
||||||
|
{whisperModelTarget && fileExists(whisperModelTarget) &&
|
||||||
|
(<Pressable onPress={doDelete} style={styles.deleteButton}>
|
||||||
|
<Text style={styles.buttonText}>Delete</Text>
|
||||||
|
</Pressable>)
|
||||||
|
}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@ -257,14 +299,26 @@ const SettingsComponent: React.FC = () => {
|
|||||||
|
|
||||||
// Create styles for the component
|
// Create styles for the component
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
downloadButtonWrapper: {
|
||||||
backgroundColor: "blue",
|
flex: 1,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
display: "flex",
|
|
||||||
flexShrink: 1,
|
|
||||||
padding: 20,
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
alignContent: "center",
|
verticalAlign: "middle",
|
||||||
|
},
|
||||||
|
downloadButton: {
|
||||||
|
backgroundColor: "blue",
|
||||||
|
padding: 10,
|
||||||
|
margin: 10,
|
||||||
|
},
|
||||||
|
deleteButton: {
|
||||||
|
backgroundColor: "darkred",
|
||||||
|
padding: 10,
|
||||||
|
margin: 10,
|
||||||
|
},
|
||||||
|
pauseDownloadButton: {
|
||||||
|
backgroundColor: "#444444",
|
||||||
|
padding: 10,
|
||||||
|
margin: 10,
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
color: "white",
|
color: "white",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user