@umituz/react-native-ai-generation-content 1.84.3 → 1.84.5

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.
Files changed (20) hide show
  1. package/package.json +2 -1
  2. package/src/domain/entities/flow-step.types.ts +1 -0
  3. package/src/domains/creations/presentation/components/GalleryResultPreview.tsx +4 -0
  4. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +1 -0
  5. package/src/domains/generation/wizard/configs/index.ts +1 -0
  6. package/src/domains/generation/wizard/configs/solo-video.config.ts +45 -0
  7. package/src/domains/generation/wizard/domain/entities/wizard-step.types.ts +12 -0
  8. package/src/domains/generation/wizard/index.ts +5 -1
  9. package/src/domains/generation/wizard/infrastructure/builders/dynamic-step-builder.ts +1 -0
  10. package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +4 -0
  11. package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.utils.ts +7 -0
  12. package/src/domains/generation/wizard/presentation/components/step-renderers/renderAudioPickerStep.tsx +56 -0
  13. package/src/domains/generation/wizard/presentation/screens/AudioPickerScreen.tsx +222 -0
  14. package/src/domains/generation/wizard/presentation/screens/AudioPickerScreen.types.ts +30 -0
  15. package/src/domains/generation/wizard/presentation/screens/index.ts +5 -0
  16. package/src/domains/result-preview/presentation/components/ResultActionBar.tsx +10 -0
  17. package/src/domains/result-preview/presentation/components/ResultPreviewScreen.tsx +2 -0
  18. package/src/domains/result-preview/presentation/types/result-components.types.ts +2 -0
  19. package/src/domains/result-preview/presentation/types/result-screen.types.ts +2 -0
  20. 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",
3
+ "version": "1.84.5",
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",
@@ -17,6 +17,7 @@ export enum StepType {
17
17
  PARTNER_UPLOAD = "partner_upload",
18
18
  TEXT_INPUT = "text_input",
19
19
  FEATURE_SELECTION = "feature_selection",
20
+ AUDIO_PICKER = "audio_picker",
20
21
  // Generation steps
21
22
  GENERATING = "generating",
22
23
  RESULT_PREVIEW = "result_preview",
@@ -27,6 +27,8 @@ interface GalleryResultPreviewProps {
27
27
  readonly onEdit?: (imageUrl: string) => void;
28
28
  /** Called when the user taps Edit on a video creation. */
29
29
  readonly onEditVideo?: (videoUrl: string) => void;
30
+ /** Called when the user taps Post to Feed. Omit to hide (apps without a feed). */
31
+ readonly onShareToFeed?: (creation: Creation) => void;
30
32
  }
31
33
 
32
34
  export function GalleryResultPreview({
@@ -43,6 +45,7 @@ export function GalleryResultPreview({
43
45
  onCloseRating,
44
46
  onEdit,
45
47
  onEditVideo,
48
+ onShareToFeed,
46
49
  }: GalleryResultPreviewProps) {
47
50
  const alert = useAlert();
48
51
 
@@ -69,6 +72,7 @@ export function GalleryResultPreview({
69
72
  onRate={onRate}
70
73
  onEdit={!videoUrl && imageUrl && onEdit ? () => onEdit(imageUrl) : undefined}
71
74
  onEditVideo={videoUrl && onEditVideo ? () => onEditVideo(videoUrl) : undefined}
75
+ onShareToFeed={onShareToFeed ? () => onShareToFeed(selectedCreation) : undefined}
72
76
  hideLabel
73
77
  iconOnly
74
78
  showTryAgain
@@ -189,6 +189,7 @@ export function CreationsGalleryScreen({
189
189
  onCloseRating={() => galleryState.setShowRatingPicker(false)}
190
190
  onEdit={onEdit}
191
191
  onEditVideo={onEditVideo}
192
+ onShareToFeed={onShareToFeed}
192
193
  />
193
194
  );
194
195
  }
@@ -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";
@@ -20,6 +20,7 @@ const convertToFlowStep = (wizardStep: WizardStepConfig): StepDefinition => {
20
20
  text_input: StepType.TEXT_INPUT,
21
21
  selection: StepType.FEATURE_SELECTION,
22
22
  preview: StepType.SCENARIO_PREVIEW,
23
+ audio_picker: StepType.AUDIO_PICKER,
23
24
  };
24
25
 
25
26
  return {
@@ -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="ghost"
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
+ }
@@ -5,3 +5,8 @@ export type {
5
5
  TextInputScreenConfig,
6
6
  TextInputScreenProps,
7
7
  } from "./TextInputScreen";
8
+ export { AudioPickerScreen } from "./AudioPickerScreen";
9
+ export type {
10
+ AudioPickerScreenTranslations,
11
+ AudioPickerScreenProps,
12
+ } from "./AudioPickerScreen";
@@ -25,6 +25,7 @@ export const ResultActionBar: React.FC<ResultActionBarProps> = ({
25
25
  showRating = false,
26
26
  onEdit,
27
27
  onEditVideo,
28
+ onShareToFeed,
28
29
  }) => {
29
30
  const tokens = useAppDesignTokens();
30
31
  const { minTouchTarget } = useResponsive();
@@ -134,6 +135,15 @@ export const ResultActionBar: React.FC<ResultActionBarProps> = ({
134
135
  <AtomicIcon name="video" customSize={20} color="onPrimary" />
135
136
  </TouchableOpacity>
136
137
  )}
138
+ {onShareToFeed && (
139
+ <TouchableOpacity
140
+ style={styles.iconButton}
141
+ onPress={onShareToFeed}
142
+ activeOpacity={0.7}
143
+ >
144
+ <AtomicIcon name="send" customSize={20} color="onPrimary" />
145
+ </TouchableOpacity>
146
+ )}
137
147
  </View>
138
148
  );
139
149
  }
@@ -23,6 +23,7 @@ export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
23
23
  onRate,
24
24
  onEdit,
25
25
  onEditVideo,
26
+ onShareToFeed,
26
27
  recentCreations,
27
28
  onViewAll,
28
29
  onCreationPress,
@@ -75,6 +76,7 @@ export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
75
76
  onRate={onRate}
76
77
  onEdit={onEdit}
77
78
  onEditVideo={onEditVideo}
79
+ onShareToFeed={onShareToFeed}
78
80
  saveButtonText={translations.saveButton}
79
81
  shareButtonText={translations.shareButton}
80
82
  tryAgainButtonText={translations.tryAnother}
@@ -48,4 +48,6 @@ export interface ResultActionBarProps {
48
48
  onEdit?: () => void;
49
49
  /** Edit video button callback — only shown in iconOnly mode when provided */
50
50
  onEditVideo?: () => void;
51
+ /** Post to feed callback — when provided, shows a "send" button. Omit to hide (apps without a feed). */
52
+ onShareToFeed?: () => void;
51
53
  }
@@ -26,6 +26,8 @@ export interface ResultPreviewScreenProps {
26
26
  onEdit?: () => void;
27
27
  /** Edit video callback — opens video editor for the result video */
28
28
  onEditVideo?: () => void;
29
+ /** Post to feed callback — when provided, shows a send button. Omit to hide (apps without a feed). */
30
+ onShareToFeed?: () => void;
29
31
  /** Recent creations to display */
30
32
  recentCreations?: readonly RecentCreation[];
31
33
  /** Navigate to all creations */
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export {
29
29
  TEXT_TO_IMAGE_WIZARD_CONFIG,
30
30
  TEXT_TO_VIDEO_WIZARD_CONFIG,
31
31
  IMAGE_TO_VIDEO_WIZARD_CONFIG,
32
+ SOLO_VIDEO_WIZARD_CONFIG,
32
33
  } from "./domains/generation/wizard";
33
34
  export type { WizardScenarioData } from "./domains/generation/wizard";
34
35