@umituz/react-native-ai-generation-content 1.84.4 → 1.84.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/domain/entities/flow-step.types.ts +1 -0
- package/src/domains/generation/wizard/configs/index.ts +1 -0
- package/src/domains/generation/wizard/configs/solo-video.config.ts +45 -0
- package/src/domains/generation/wizard/domain/entities/wizard-step.types.ts +12 -0
- package/src/domains/generation/wizard/index.ts +5 -1
- package/src/domains/generation/wizard/infrastructure/builders/dynamic-step-builder.ts +1 -0
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +4 -0
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.utils.ts +7 -0
- package/src/domains/generation/wizard/presentation/components/step-renderers/renderAudioPickerStep.tsx +56 -0
- package/src/domains/generation/wizard/presentation/screens/AudioPickerScreen.tsx +222 -0
- package/src/domains/generation/wizard/presentation/screens/AudioPickerScreen.types.ts +30 -0
- package/src/domains/generation/wizard/presentation/screens/index.ts +5 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.84.
|
|
3
|
+
"version": "1.84.6",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"expo-clipboard": "^8.0.8",
|
|
85
85
|
"expo-crypto": "^15.0.8",
|
|
86
86
|
"expo-device": "^8.0.10",
|
|
87
|
+
"expo-document-picker": "^14.0.8",
|
|
87
88
|
"expo-file-system": "^19.0.21",
|
|
88
89
|
"expo-font": "^14.0.10",
|
|
89
90
|
"expo-haptics": "^15.0.8",
|
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
export { TEXT_TO_IMAGE_WIZARD_CONFIG } from "./text-to-image.config";
|
|
7
7
|
export { TEXT_TO_VIDEO_WIZARD_CONFIG } from "./text-to-video.config";
|
|
8
8
|
export { IMAGE_TO_VIDEO_WIZARD_CONFIG } from "./image-to-video.config";
|
|
9
|
+
export { SOLO_VIDEO_WIZARD_CONFIG } from "./solo-video.config";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solo Video Wizard Config
|
|
3
|
+
* Flow: Photo → Prompt (with info about two-step generation) → Audio (optional) → Generation
|
|
4
|
+
*
|
|
5
|
+
* Two-step generation:
|
|
6
|
+
* 1. Generate image from photo + prompt (nano-banana-2/edit)
|
|
7
|
+
* 2. Generate video from that image (I2V model)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { WizardFeatureConfig } from "../domain/entities/wizard-feature.types";
|
|
11
|
+
|
|
12
|
+
export const SOLO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
13
|
+
id: "solo-video",
|
|
14
|
+
name: "Solo Video",
|
|
15
|
+
steps: [
|
|
16
|
+
{
|
|
17
|
+
id: "photo_1",
|
|
18
|
+
type: "photo_upload",
|
|
19
|
+
titleKey: "soloVideo.selectPhoto",
|
|
20
|
+
subtitleKey: "soloVideo.selectPhotoHint",
|
|
21
|
+
showFaceDetection: false,
|
|
22
|
+
showPhotoTips: true,
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "video_prompt",
|
|
27
|
+
type: "text_input",
|
|
28
|
+
titleKey: "soloVideo.prompt",
|
|
29
|
+
subtitleKey: "soloVideo.promptInfo",
|
|
30
|
+
placeholderKey: "soloVideo.promptPlaceholder",
|
|
31
|
+
required: true,
|
|
32
|
+
minLength: 3,
|
|
33
|
+
maxLength: 500,
|
|
34
|
+
multiline: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "background_audio",
|
|
38
|
+
type: "audio_picker",
|
|
39
|
+
titleKey: "soloVideo.audioTitle",
|
|
40
|
+
subtitleKey: "soloVideo.audioSubtitle",
|
|
41
|
+
required: false,
|
|
42
|
+
maxFileSizeMB: 20,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
@@ -84,6 +84,17 @@ export interface CreditGateStepConfig extends BaseStepConfig {
|
|
|
84
84
|
readonly messageKey?: string;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Audio Picker Step Configuration
|
|
89
|
+
*/
|
|
90
|
+
export interface AudioPickerStepConfig extends BaseStepConfig {
|
|
91
|
+
readonly type: "audio_picker";
|
|
92
|
+
/** Allowed MIME types (default: audio/mpeg, audio/mp4, audio/wav, audio/aac) */
|
|
93
|
+
readonly allowedTypes?: readonly string[];
|
|
94
|
+
/** Max file size in MB (default: 20) */
|
|
95
|
+
readonly maxFileSizeMB?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
/**
|
|
88
99
|
* Union of all step config types
|
|
89
100
|
*/
|
|
@@ -94,4 +105,5 @@ export type WizardStepConfig =
|
|
|
94
105
|
| TextInputStepConfig
|
|
95
106
|
| SelectionStepConfig
|
|
96
107
|
| PreviewStepConfig
|
|
108
|
+
| AudioPickerStepConfig
|
|
97
109
|
| BaseStepConfig;
|
|
@@ -13,6 +13,7 @@ export type {
|
|
|
13
13
|
TextInputStepConfig,
|
|
14
14
|
SelectionStepConfig,
|
|
15
15
|
PreviewStepConfig,
|
|
16
|
+
AudioPickerStepConfig,
|
|
16
17
|
WizardStepConfig,
|
|
17
18
|
} from "./domain/entities/wizard-step.types";
|
|
18
19
|
|
|
@@ -70,11 +71,13 @@ export { GenericWizardFlow } from "./presentation/components";
|
|
|
70
71
|
export type { GenericWizardFlowProps } from "./presentation/components";
|
|
71
72
|
|
|
72
73
|
// Presentation - Screens
|
|
73
|
-
export { GeneratingScreen, TextInputScreen } from "./presentation/screens";
|
|
74
|
+
export { GeneratingScreen, TextInputScreen, AudioPickerScreen } from "./presentation/screens";
|
|
74
75
|
export type {
|
|
75
76
|
TextInputScreenTranslations,
|
|
76
77
|
TextInputScreenConfig,
|
|
77
78
|
TextInputScreenProps,
|
|
79
|
+
AudioPickerScreenTranslations,
|
|
80
|
+
AudioPickerScreenProps,
|
|
78
81
|
} from "./presentation/screens";
|
|
79
82
|
|
|
80
83
|
// Feature Configs
|
|
@@ -82,4 +85,5 @@ export {
|
|
|
82
85
|
TEXT_TO_IMAGE_WIZARD_CONFIG,
|
|
83
86
|
TEXT_TO_VIDEO_WIZARD_CONFIG,
|
|
84
87
|
IMAGE_TO_VIDEO_WIZARD_CONFIG,
|
|
88
|
+
SOLO_VIDEO_WIZARD_CONFIG,
|
|
85
89
|
} from "./configs";
|
|
@@ -12,6 +12,7 @@ import { renderPreviewStep } from "./step-renderers/renderPreviewStep";
|
|
|
12
12
|
import { renderPhotoUploadStep } from "./step-renderers/renderPhotoUploadStep";
|
|
13
13
|
import { renderTextInputStep } from "./step-renderers/renderTextInputStep";
|
|
14
14
|
import { renderSelectionStep } from "./step-renderers/renderSelectionStep";
|
|
15
|
+
import { renderAudioPickerStep } from "./step-renderers/renderAudioPickerStep";
|
|
15
16
|
import type { WizardStepRendererProps } from "./WizardStepRenderer.types";
|
|
16
17
|
|
|
17
18
|
export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
@@ -97,6 +98,9 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
97
98
|
case StepType.FEATURE_SELECTION:
|
|
98
99
|
return renderSelectionStep({ key: step.id, step, customData, onBack, onPhotoContinue, calculateCreditForSelection, t, creditCost });
|
|
99
100
|
|
|
101
|
+
case StepType.AUDIO_PICKER:
|
|
102
|
+
return renderAudioPickerStep({ key: step.id, step, onBack, onPhotoContinue, t, creditCost });
|
|
103
|
+
|
|
100
104
|
default:
|
|
101
105
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
106
|
console.warn("[WizardStepRenderer] Unhandled step type", { stepType: step.type });
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
WizardStepConfig,
|
|
3
3
|
TextInputStepConfig,
|
|
4
4
|
SelectionStepConfig,
|
|
5
|
+
AudioPickerStepConfig,
|
|
5
6
|
} from "../../domain/entities/wizard-step.types";
|
|
6
7
|
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
7
8
|
|
|
@@ -51,3 +52,9 @@ export function getTextInputValue(data: unknown): string | undefined {
|
|
|
51
52
|
if (isRecord(data) && "text" in data) return String(data.text);
|
|
52
53
|
return undefined;
|
|
53
54
|
}
|
|
55
|
+
|
|
56
|
+
export function getAudioPickerConfig(config: unknown): AudioPickerStepConfig | undefined {
|
|
57
|
+
if (!isRecord(config)) return undefined;
|
|
58
|
+
if (config.type === "audio_picker") return config as unknown as AudioPickerStepConfig;
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio Picker Step Renderer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { AudioPickerScreen } from "../../screens/AudioPickerScreen";
|
|
7
|
+
import { getAudioPickerConfig } from "../WizardStepRenderer.utils";
|
|
8
|
+
import type { StepDefinition } from "../../../../../../domain/entities/flow-config.types";
|
|
9
|
+
import type { UploadedImage } from "../../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
10
|
+
|
|
11
|
+
interface AudioPickerStepProps {
|
|
12
|
+
readonly key?: string;
|
|
13
|
+
readonly step: StepDefinition;
|
|
14
|
+
readonly onBack: () => void;
|
|
15
|
+
readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
|
|
16
|
+
readonly t: (key: string) => string;
|
|
17
|
+
readonly creditCost?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function renderAudioPickerStep({
|
|
21
|
+
step,
|
|
22
|
+
onBack,
|
|
23
|
+
onPhotoContinue,
|
|
24
|
+
t,
|
|
25
|
+
creditCost,
|
|
26
|
+
}: AudioPickerStepProps): React.ReactElement {
|
|
27
|
+
const audioConfig = getAudioPickerConfig(step.config);
|
|
28
|
+
const titleKey = audioConfig?.titleKey ?? `wizard.steps.${step.id}.title`;
|
|
29
|
+
const subtitleKey = audioConfig?.subtitleKey ?? `wizard.steps.${step.id}.subtitle`;
|
|
30
|
+
const isOptional = !(step.required ?? true);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<AudioPickerScreen
|
|
34
|
+
key={step.id}
|
|
35
|
+
stepId={step.id}
|
|
36
|
+
translations={{
|
|
37
|
+
title: t(titleKey),
|
|
38
|
+
subtitle: subtitleKey ? t(subtitleKey) : undefined,
|
|
39
|
+
selectButton: t("audioPicker.selectFile"),
|
|
40
|
+
skipButton: t("audioPicker.skip"),
|
|
41
|
+
continueButton: t("common.continue"),
|
|
42
|
+
selectedLabel: t("audioPicker.selected"),
|
|
43
|
+
fileTooLarge: t("common.errors.file_too_large"),
|
|
44
|
+
unsupportedFormat: t("audioPicker.unsupportedFormat"),
|
|
45
|
+
}}
|
|
46
|
+
allowedTypes={audioConfig?.allowedTypes as string[] | undefined}
|
|
47
|
+
maxFileSizeMB={audioConfig?.maxFileSizeMB}
|
|
48
|
+
optional={isOptional}
|
|
49
|
+
creditCost={creditCost}
|
|
50
|
+
onBack={onBack}
|
|
51
|
+
onContinue={(audioUri) => {
|
|
52
|
+
onPhotoContinue(step.id, { uri: audioUri, previewUrl: "" } as UploadedImage);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPickerScreen
|
|
3
|
+
* Allows users to pick an audio file (mp3, m4a, wav) for video generation.
|
|
4
|
+
* Supports optional skip. Uses expo-document-picker.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback, useMemo } from "react";
|
|
8
|
+
import { View, StyleSheet } from "react-native";
|
|
9
|
+
import * as DocumentPicker from "expo-document-picker";
|
|
10
|
+
import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
11
|
+
import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
|
|
12
|
+
import { NavigationHeader } from "@umituz/react-native-design-system/molecules";
|
|
13
|
+
import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
14
|
+
import { WizardContinueButton } from "../components/WizardContinueButton";
|
|
15
|
+
import type { AudioPickerScreenProps } from "./AudioPickerScreen.types";
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
AudioPickerScreenTranslations,
|
|
19
|
+
AudioPickerScreenProps,
|
|
20
|
+
} from "./AudioPickerScreen.types";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_AUDIO_TYPES = [
|
|
23
|
+
"audio/mpeg",
|
|
24
|
+
"audio/mp4",
|
|
25
|
+
"audio/wav",
|
|
26
|
+
"audio/aac",
|
|
27
|
+
"audio/x-m4a",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const DEFAULT_MAX_SIZE_MB = 20;
|
|
31
|
+
|
|
32
|
+
export const AudioPickerScreen: React.FC<AudioPickerScreenProps> = ({
|
|
33
|
+
stepId: _stepId,
|
|
34
|
+
translations,
|
|
35
|
+
allowedTypes,
|
|
36
|
+
maxFileSizeMB,
|
|
37
|
+
optional = true,
|
|
38
|
+
creditCost,
|
|
39
|
+
onBack,
|
|
40
|
+
onContinue,
|
|
41
|
+
}) => {
|
|
42
|
+
const tokens = useAppDesignTokens();
|
|
43
|
+
const [selectedFile, setSelectedFile] = useState<{
|
|
44
|
+
uri: string;
|
|
45
|
+
name: string;
|
|
46
|
+
size?: number;
|
|
47
|
+
} | null>(null);
|
|
48
|
+
const [error, setError] = useState<string | null>(null);
|
|
49
|
+
|
|
50
|
+
const maxSize = (maxFileSizeMB ?? DEFAULT_MAX_SIZE_MB) * 1024 * 1024;
|
|
51
|
+
const mimeTypes = allowedTypes ?? DEFAULT_AUDIO_TYPES;
|
|
52
|
+
|
|
53
|
+
const handlePick = useCallback(async () => {
|
|
54
|
+
try {
|
|
55
|
+
setError(null);
|
|
56
|
+
const result = await DocumentPicker.getDocumentAsync({
|
|
57
|
+
type: mimeTypes as string[],
|
|
58
|
+
copyToCacheDirectory: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result.canceled || !result.assets?.length) return;
|
|
62
|
+
|
|
63
|
+
const asset = result.assets[0];
|
|
64
|
+
|
|
65
|
+
if (asset.size && asset.size > maxSize) {
|
|
66
|
+
setError(
|
|
67
|
+
translations.fileTooLarge ??
|
|
68
|
+
`File too large. Max ${maxFileSizeMB ?? DEFAULT_MAX_SIZE_MB}MB.`
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setSelectedFile({
|
|
74
|
+
uri: asset.uri,
|
|
75
|
+
name: asset.name,
|
|
76
|
+
size: asset.size ?? undefined,
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
if (__DEV__) {
|
|
80
|
+
console.warn("[AudioPickerScreen] Failed to pick document");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, [mimeTypes, maxSize, maxFileSizeMB, translations]);
|
|
84
|
+
|
|
85
|
+
const handleContinue = useCallback(() => {
|
|
86
|
+
onContinue(selectedFile?.uri ?? "");
|
|
87
|
+
}, [selectedFile, onContinue]);
|
|
88
|
+
|
|
89
|
+
const handleSkip = useCallback(() => {
|
|
90
|
+
onContinue("");
|
|
91
|
+
}, [onContinue]);
|
|
92
|
+
|
|
93
|
+
const formatFileSize = useCallback((bytes?: number) => {
|
|
94
|
+
if (!bytes) return "";
|
|
95
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
96
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
97
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const canContinue = !!selectedFile || optional;
|
|
101
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<View style={{ flex: 1, backgroundColor: tokens.colors.backgroundPrimary }}>
|
|
105
|
+
<NavigationHeader
|
|
106
|
+
title=""
|
|
107
|
+
onBackPress={onBack}
|
|
108
|
+
rightElement={
|
|
109
|
+
<WizardContinueButton
|
|
110
|
+
label={translations.continueButton}
|
|
111
|
+
canContinue={canContinue}
|
|
112
|
+
onPress={handleContinue}
|
|
113
|
+
creditCost={creditCost}
|
|
114
|
+
/>
|
|
115
|
+
}
|
|
116
|
+
/>
|
|
117
|
+
<ScreenLayout
|
|
118
|
+
scrollable={true}
|
|
119
|
+
edges={["left", "right"]}
|
|
120
|
+
hideScrollIndicator={true}
|
|
121
|
+
contentContainerStyle={styles.scrollContent}
|
|
122
|
+
>
|
|
123
|
+
<AtomicText type="headlineMedium" color="textPrimary" style={styles.title}>
|
|
124
|
+
{translations.title}
|
|
125
|
+
</AtomicText>
|
|
126
|
+
|
|
127
|
+
{translations.subtitle ? (
|
|
128
|
+
<AtomicText type="bodyMedium" color="textSecondary" style={styles.subtitle}>
|
|
129
|
+
{translations.subtitle}
|
|
130
|
+
</AtomicText>
|
|
131
|
+
) : null}
|
|
132
|
+
|
|
133
|
+
{selectedFile ? (
|
|
134
|
+
<View style={styles.selectedContainer}>
|
|
135
|
+
<AtomicText type="labelLarge" color="textPrimary" style={styles.fileName}>
|
|
136
|
+
{selectedFile.name}
|
|
137
|
+
</AtomicText>
|
|
138
|
+
{selectedFile.size ? (
|
|
139
|
+
<AtomicText type="bodySmall" color="textTertiary">
|
|
140
|
+
{formatFileSize(selectedFile.size)}
|
|
141
|
+
</AtomicText>
|
|
142
|
+
) : null}
|
|
143
|
+
<AtomicButton
|
|
144
|
+
variant="outline"
|
|
145
|
+
size="sm"
|
|
146
|
+
onPress={handlePick}
|
|
147
|
+
style={styles.changeButton}
|
|
148
|
+
>
|
|
149
|
+
{translations.selectButton}
|
|
150
|
+
</AtomicButton>
|
|
151
|
+
</View>
|
|
152
|
+
) : (
|
|
153
|
+
<AtomicButton
|
|
154
|
+
variant="outline"
|
|
155
|
+
size="md"
|
|
156
|
+
onPress={handlePick}
|
|
157
|
+
style={styles.pickButton}
|
|
158
|
+
>
|
|
159
|
+
{translations.selectButton}
|
|
160
|
+
</AtomicButton>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{error ? (
|
|
164
|
+
<AtomicText type="bodySmall" color="error" style={styles.error}>
|
|
165
|
+
{error}
|
|
166
|
+
</AtomicText>
|
|
167
|
+
) : null}
|
|
168
|
+
|
|
169
|
+
{optional && !selectedFile ? (
|
|
170
|
+
<AtomicButton
|
|
171
|
+
variant="text"
|
|
172
|
+
size="sm"
|
|
173
|
+
onPress={handleSkip}
|
|
174
|
+
style={styles.skipButton}
|
|
175
|
+
>
|
|
176
|
+
{translations.skipButton}
|
|
177
|
+
</AtomicButton>
|
|
178
|
+
) : null}
|
|
179
|
+
</ScreenLayout>
|
|
180
|
+
</View>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const createStyles = (tokens: DesignTokens) =>
|
|
185
|
+
StyleSheet.create({
|
|
186
|
+
scrollContent: {
|
|
187
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
188
|
+
paddingBottom: 40,
|
|
189
|
+
},
|
|
190
|
+
title: {
|
|
191
|
+
marginBottom: tokens.spacing.sm,
|
|
192
|
+
},
|
|
193
|
+
subtitle: {
|
|
194
|
+
marginBottom: tokens.spacing.xl,
|
|
195
|
+
},
|
|
196
|
+
selectedContainer: {
|
|
197
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
198
|
+
borderRadius: tokens.borders.radius.md,
|
|
199
|
+
borderWidth: 1,
|
|
200
|
+
borderColor: tokens.colors.border,
|
|
201
|
+
padding: tokens.spacing.lg,
|
|
202
|
+
alignItems: "center",
|
|
203
|
+
gap: tokens.spacing.sm,
|
|
204
|
+
},
|
|
205
|
+
fileName: {
|
|
206
|
+
textAlign: "center",
|
|
207
|
+
},
|
|
208
|
+
changeButton: {
|
|
209
|
+
marginTop: tokens.spacing.sm,
|
|
210
|
+
},
|
|
211
|
+
pickButton: {
|
|
212
|
+
marginTop: tokens.spacing.md,
|
|
213
|
+
},
|
|
214
|
+
error: {
|
|
215
|
+
marginTop: tokens.spacing.sm,
|
|
216
|
+
textAlign: "center",
|
|
217
|
+
},
|
|
218
|
+
skipButton: {
|
|
219
|
+
marginTop: tokens.spacing.lg,
|
|
220
|
+
alignSelf: "center",
|
|
221
|
+
},
|
|
222
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPickerScreen Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface AudioPickerScreenTranslations {
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly subtitle?: string;
|
|
8
|
+
readonly selectButton: string;
|
|
9
|
+
readonly skipButton: string;
|
|
10
|
+
readonly continueButton: string;
|
|
11
|
+
readonly selectedLabel: string;
|
|
12
|
+
readonly fileTooLarge?: string;
|
|
13
|
+
readonly unsupportedFormat?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AudioPickerScreenProps {
|
|
17
|
+
readonly stepId: string;
|
|
18
|
+
readonly translations: AudioPickerScreenTranslations;
|
|
19
|
+
/** Allowed MIME types */
|
|
20
|
+
readonly allowedTypes?: readonly string[];
|
|
21
|
+
/** Max file size in MB */
|
|
22
|
+
readonly maxFileSizeMB?: number;
|
|
23
|
+
/** Whether this step can be skipped */
|
|
24
|
+
readonly optional?: boolean;
|
|
25
|
+
/** Calculated credit cost from parent */
|
|
26
|
+
readonly creditCost?: number;
|
|
27
|
+
readonly onBack: () => void;
|
|
28
|
+
/** Called with audio URI, or empty string if skipped */
|
|
29
|
+
readonly onContinue: (audioUri: string) => void;
|
|
30
|
+
}
|
package/src/index.ts
CHANGED