Skip to content

Commit a1bbda3

Browse files
committed
Redesign demo app
1 parent ac7e754 commit a1bbda3

3 files changed

Lines changed: 253 additions & 156 deletions

File tree

apps/computer-vision/app/text_to_image/index.tsx

Lines changed: 85 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,23 @@ import {
33
StyleSheet,
44
Text,
55
Image,
6-
TextInput,
7-
TouchableOpacity,
8-
KeyboardAvoidingView,
9-
Platform,
10-
TouchableWithoutFeedback,
116
Keyboard,
7+
TouchableWithoutFeedback,
128
} from 'react-native';
139
import React, { useContext, useEffect, useState } from 'react';
1410
import Spinner from 'react-native-loading-spinner-overlay';
1511
import { useTextToImage, BK_SDM_TINY_VPRED } from 'react-native-executorch';
1612
import { GeneratingContext } from '../../context';
1713
import ColorPalette from '../../colors';
1814
import ProgressBar from '../../components/ProgressBar';
19-
20-
type InputState =
21-
| { kind: 'prompt'; value: string }
22-
| { kind: 'image'; uri: string };
15+
import { BottomBarWithTextInput } from '../../components/BottomBarWithTextInput';
2316

2417
export default function TextToImageScreen() {
2518
const [inferenceStepIdx, setInferenceStepIdx] = useState<number>(0);
2619
const [imageTitle, setImageTitle] = useState<string | null>(null);
27-
const [numSteps, setNumSteps] = useState<number>(10);
20+
const [image, setImage] = useState<string | null>(null);
21+
const [steps, setSteps] = useState<number>(10);
22+
const [showTextInput, setShowTextInput] = useState(false);
2823
const [keyboardVisible, setKeyboardVisible] = useState(false);
2924

3025
const imageSize = 352;
@@ -35,42 +30,37 @@ export default function TextToImageScreen() {
3530

3631
const { setGlobalGenerating } = useContext(GeneratingContext);
3732

38-
const [inputState, setInputState] = useState<InputState>({
39-
kind: 'prompt',
40-
value: '',
41-
});
42-
4333
useEffect(() => {
4434
setGlobalGenerating(model.isGenerating);
4535
}, [model.isGenerating, setGlobalGenerating]);
4636

4737
useEffect(() => {
48-
const onKeyboardShow = Keyboard.addListener('keyboardDidShow', () =>
49-
setKeyboardVisible(true)
50-
);
51-
const onKeyboardHide = Keyboard.addListener('keyboardDidHide', () =>
52-
setKeyboardVisible(false)
53-
);
38+
const showSub = Keyboard.addListener('keyboardDidShow', () => {
39+
setKeyboardVisible(true);
40+
});
41+
const hideSub = Keyboard.addListener('keyboardDidHide', () => {
42+
setKeyboardVisible(false);
43+
});
5444
return () => {
55-
onKeyboardShow.remove();
56-
onKeyboardHide.remove();
45+
showSub.remove();
46+
hideSub.remove();
5747
};
5848
}, []);
5949

60-
const runForward = async () => {
61-
if (inputState.kind !== 'prompt' || !inputState.value.trim()) return;
62-
setImageTitle(inputState.value);
50+
const runForward = async (input: string, numSteps: number) => {
51+
if (!input || !input.trim()) return;
52+
setImageTitle(input);
53+
setSteps(numSteps);
6354
try {
64-
const output = await model.generate(
65-
inputState.value,
66-
imageSize,
67-
numSteps
68-
);
55+
const output = await model.generate(input, imageSize, steps);
56+
console.log('Is output?', !!output);
57+
console.log(output);
6958
if (!output.length) {
59+
console.log('interrupted');
7060
setImageTitle(null);
7161
return;
7262
}
73-
setInputState({ kind: 'image', uri: output });
63+
setImage(output);
7464
} catch (e) {
7565
console.error(e);
7666
setImageTitle(null);
@@ -79,9 +69,6 @@ export default function TextToImageScreen() {
7969
}
8070
};
8171

82-
const decreaseSteps = () => setNumSteps((prev) => Math.max(5, prev - 5));
83-
const increaseSteps = () => setNumSteps((prev) => Math.min(50, prev + 5));
84-
8572
if (!model.isReady) {
8673
return (
8774
<Spinner
@@ -91,24 +78,31 @@ export default function TextToImageScreen() {
9178
);
9279
}
9380

94-
if (!model.isGenerating) {
95-
return (
96-
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
97-
<KeyboardAvoidingView
98-
style={styles.container}
99-
collapsable={false}
100-
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
101-
keyboardVerticalOffset={Platform.OS === 'ios' ? 120 : 40}
102-
>
103-
<View style={styles.titleContainer}>
104-
{imageTitle && <Text style={styles.titleText}>{imageTitle}</Text>}
105-
</View>
81+
return (
82+
<TouchableWithoutFeedback
83+
onPress={() => {
84+
Keyboard.dismiss();
85+
setShowTextInput(false);
86+
}}
87+
>
88+
<View style={styles.container}>
89+
{keyboardVisible && <View style={styles.overlay} />}
90+
91+
<View style={styles.titleContainer}>
92+
{imageTitle && <Text style={styles.titleText}>{imageTitle}</Text>}
93+
</View>
10694

95+
{model.isGenerating ? (
96+
<View style={styles.progressContainer}>
97+
<Text style={styles.text}>Generating...</Text>
98+
<ProgressBar numSteps={steps} currentStep={inferenceStepIdx} />
99+
</View>
100+
) : (
107101
<View style={styles.imageContainer}>
108-
{inputState.kind === 'image' ? (
102+
{image?.length ? (
109103
<Image
110104
style={styles.image}
111-
source={{ uri: `data:image/png;base64,${inputState.uri}` }}
105+
source={{ uri: `data:image/png;base64,${image}` }}
112106
/>
113107
) : (
114108
<Image
@@ -117,103 +111,38 @@ export default function TextToImageScreen() {
117111
/>
118112
)}
119113
</View>
114+
)}
120115

121-
<View style={styles.bottomContainer}>
122-
<TextInput
123-
style={styles.input}
124-
placeholder="Enter prompt..."
125-
value={inputState.kind === 'prompt' ? inputState.value : ''}
126-
onChangeText={(text) => {
127-
setInputState({ kind: 'prompt', value: text });
128-
setImageTitle(null);
129-
}}
130-
editable={!model.isGenerating}
131-
/>
132-
{!keyboardVisible && (
133-
<View style={styles.stepsContainer}>
134-
<Text style={styles.text}>Steps: {numSteps}</Text>
135-
<View style={styles.stepsButtons}>
136-
<TouchableOpacity
137-
style={[styles.button, styles.stepsButton]}
138-
onPress={decreaseSteps}
139-
>
140-
<Text style={styles.buttonText}>-</Text>
141-
</TouchableOpacity>
142-
<TouchableOpacity
143-
style={[styles.button, styles.stepsButton]}
144-
onPress={increaseSteps}
145-
>
146-
<Text style={styles.buttonText}>+</Text>
147-
</TouchableOpacity>
148-
</View>
149-
</View>
150-
)}
151-
<TouchableOpacity
152-
style={styles.button}
153-
onPress={runForward}
154-
disabled={!model.isReady}
155-
>
156-
<Text style={styles.buttonText}>Run model</Text>
157-
</TouchableOpacity>
158-
</View>
159-
</KeyboardAvoidingView>
160-
</TouchableWithoutFeedback>
161-
);
162-
}
163-
164-
return (
165-
<KeyboardAvoidingView
166-
style={styles.container}
167-
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
168-
keyboardVerticalOffset={Platform.OS === 'ios' ? 120 : 40}
169-
>
170-
<View style={styles.titleContainer}>
171-
{imageTitle && <Text style={styles.titleText}>{imageTitle}</Text>}
172-
</View>
173-
<View style={styles.progressContainer}>
174-
<Text style={styles.text}>Generating...</Text>
175-
<ProgressBar numSteps={numSteps} currentStep={inferenceStepIdx} />
176-
</View>
177-
<View style={styles.bottomContainer}>
178-
<TouchableOpacity
179-
style={styles.button}
180-
onPress={() => model.interrupt()}
181-
>
182-
<Text style={styles.buttonText}>Stop model</Text>
183-
</TouchableOpacity>
116+
<View style={styles.bottomContainer}>
117+
<BottomBarWithTextInput
118+
runModel={runForward}
119+
stopModel={model.interrupt}
120+
isGenerating={model.isGenerating}
121+
isReady={model.isReady}
122+
showTextInput={showTextInput}
123+
setShowTextInput={setShowTextInput}
124+
keyboardVisible={keyboardVisible}
125+
/>
126+
</View>
184127
</View>
185-
</KeyboardAvoidingView>
128+
</TouchableWithoutFeedback>
186129
);
187130
}
188131

189132
const styles = StyleSheet.create({
190133
container: {
191134
flex: 1,
192135
width: '100%',
193-
padding: 20,
194-
},
195-
titleContainer: { alignItems: 'center', marginTop: 20 },
196-
bottomContainer: {
197-
width: '100%',
198-
gap: 15,
199136
alignItems: 'center',
200-
padding: 16,
201137
},
202-
imageContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' },
203-
button: {
204-
width: '100%',
205-
height: 50,
206-
justifyContent: 'center',
207-
alignItems: 'center',
208-
backgroundColor: ColorPalette.primary,
209-
borderRadius: 8,
138+
overlay: {
139+
...StyleSheet.absoluteFillObject,
140+
backgroundColor: 'rgba(0,0,0,0.65)',
141+
zIndex: 5,
210142
},
211-
buttonText: { color: '#fff', fontSize: 16 },
212-
image: {
213-
width: 256,
214-
height: 256,
215-
marginVertical: 30,
216-
resizeMode: 'contain',
143+
titleContainer: {
144+
alignItems: 'center',
145+
marginTop: 20,
217146
},
218147
titleText: {
219148
color: ColorPalette.primary,
@@ -222,34 +151,34 @@ const styles = StyleSheet.create({
222151
marginBottom: 12,
223152
textAlign: 'center',
224153
},
225-
text: { fontSize: 16 },
226-
input: {
227-
width: '100%',
228-
height: 50,
229-
borderColor: '#ccc',
230-
borderWidth: 1,
231-
borderRadius: 8,
232-
padding: 10,
233-
marginBottom: 10,
154+
text: {
234155
fontSize: 16,
156+
color: '#000',
235157
},
236-
progressContainer: {
158+
imageContainer: {
237159
flex: 1,
238-
justifyContent: 'center',
160+
position: 'absolute',
161+
top: 100,
239162
alignItems: 'center',
163+
justifyContent: 'center',
240164
},
241-
stepsContainer: {
242-
width: '100%',
243-
flexDirection: 'row',
244-
justifyContent: 'space-between',
245-
alignItems: 'center',
165+
image: {
166+
width: 256,
167+
height: 256,
168+
marginVertical: 30,
169+
resizeMode: 'contain',
246170
},
247-
stepsButtons: {
248-
flexDirection: 'row',
249-
gap: 10,
171+
progressContainer: {
172+
flex: 1,
173+
justifyContent: 'center',
174+
alignItems: 'center',
250175
},
251-
stepsButton: {
252-
width: 40,
253-
height: 40,
176+
bottomContainer: {
177+
flex: 1,
178+
width: '90%',
179+
position: 'absolute',
180+
bottom: 0,
181+
marginBottom: 25,
182+
zIndex: 10,
254183
},
255184
});

0 commit comments

Comments
 (0)