@umituz/react-native-ai-generation-content 1.17.182 → 1.17.183

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.182",
3
+ "version": "1.17.183",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,16 +1,14 @@
1
1
  /**
2
2
  * AIHugFeature Component
3
3
  * Self-contained AI hug video feature UI component
4
- * Uses hook internally, only requires config and translations
4
+ * Uses centralized DualImageVideoFeatureLayout for consistent UX
5
5
  */
6
6
 
7
- import React, { useCallback } from "react";
8
- import { View, ScrollView, StyleSheet } from "react-native";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
7
+ import React, { useMemo } from "react";
8
+ import { View, StyleSheet } from "react-native";
10
9
  import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
11
- import { AIGenerationForm } from "../../../../presentation/components/AIGenerationForm";
12
- import { AIGenerationResult } from "../../../../presentation/components/display/AIGenerationResult";
13
- import { GenerationProgressModal } from "../../../../presentation/components/GenerationProgressModal";
10
+ import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
11
+ import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
14
12
  import { useAIHugFeature } from "../hooks";
15
13
  import type {
16
14
  AIHugTranslations,
@@ -18,23 +16,18 @@ import type {
18
16
  } from "../../domain/types";
19
17
 
20
18
  export interface AIHugFeatureProps {
21
- /** Feature configuration with provider-specific settings */
22
19
  config: AIHugFeatureConfig;
23
- /** Translations for all UI text */
24
- translations: AIHugTranslations;
25
- /** Source image picker callback */
20
+ translations: AIHugTranslations & {
21
+ modalTitle?: string;
22
+ modalMessage?: string;
23
+ modalHint?: string;
24
+ modalBackgroundHint?: string;
25
+ };
26
26
  onSelectSourceImage: () => Promise<string | null>;
27
- /** Target image picker callback */
28
27
  onSelectTargetImage: () => Promise<string | null>;
29
- /** Save video callback */
30
28
  onSaveVideo: (videoUrl: string) => Promise<void>;
31
- /** Called before processing starts. Return false to cancel. */
32
29
  onBeforeProcess?: () => Promise<boolean>;
33
- /** Optional custom processing modal renderer */
34
- renderProcessingModal?: (props: {
35
- visible: boolean;
36
- progress: number;
37
- }) => React.ReactNode;
30
+ renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
38
31
  }
39
32
 
40
33
  export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
@@ -46,8 +39,6 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
46
39
  onBeforeProcess,
47
40
  renderProcessingModal,
48
41
  }) => {
49
- const tokens = useAppDesignTokens();
50
-
51
42
  const feature = useAIHugFeature({
52
43
  config,
53
44
  onSelectSourceImage,
@@ -56,103 +47,41 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
56
47
  onBeforeProcess,
57
48
  });
58
49
 
59
- const handleProcess = useCallback(() => {
60
- void feature.process();
61
- }, [feature]);
62
-
63
- const handleSave = useCallback(() => {
64
- void feature.save();
65
- }, [feature]);
66
-
67
- const handleSelectSource = useCallback(() => {
68
- void feature.selectSourceImage();
69
- }, [feature]);
70
-
71
- const handleSelectTarget = useCallback(() => {
72
- void feature.selectTargetImage();
73
- }, [feature]);
74
-
75
- if (feature.processedVideoUrl) {
76
- return (
77
- <ScrollView
78
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
79
- contentContainerStyle={styles.content}
80
- showsVerticalScrollIndicator={false}
81
- >
82
- <AIGenerationResult
83
- successText={translations.successText}
84
- primaryAction={{
85
- label: translations.saveButtonText,
86
- onPress: handleSave,
87
- }}
88
- secondaryAction={{
89
- label: translations.tryAnotherText,
90
- onPress: feature.reset,
91
- }}
92
- />
93
- </ScrollView>
94
- );
95
- }
96
-
97
- const defaultModal = (
98
- <GenerationProgressModal
99
- visible={feature.isProcessing}
100
- progress={feature.progress}
101
- icon="sparkles"
102
- title={translations.modalTitle || "Creating your video"}
103
- message={translations.modalMessage || "AI is working its magic..."}
104
- hint={translations.modalHint || "This may take a moment"}
105
- backgroundHint={translations.modalBackgroundHint || "Continue in background"}
106
- onClose={() => {
107
- // Allow continuing in background - just close modal
108
- }}
109
- />
50
+ const modalTranslations = useMemo(
51
+ () => ({
52
+ title: translations.modalTitle || "Creating your video",
53
+ message: translations.modalMessage || "AI is working its magic...",
54
+ hint: translations.modalHint || "This may take a moment",
55
+ backgroundHint: translations.modalBackgroundHint || "Continue in background",
56
+ }),
57
+ [translations],
110
58
  );
111
59
 
112
60
  return (
113
- <>
114
- <ScrollView
115
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
116
- contentContainerStyle={styles.content}
117
- showsVerticalScrollIndicator={false}
118
- >
119
- <AIGenerationForm
120
- onGenerate={handleProcess}
121
- isGenerating={feature.isProcessing}
122
- translations={{
123
- generateButton: translations.processButtonText,
124
- generatingButton: translations.processingText,
125
- }}
126
- >
127
- <View style={styles.pickerContainer}>
128
- <DualImagePicker
129
- sourceImageUri={feature.sourceImageUri}
130
- targetImageUri={feature.targetImageUri}
131
- isDisabled={feature.isProcessing}
132
- onSelectSource={handleSelectSource}
133
- onSelectTarget={handleSelectTarget}
134
- sourcePlaceholder={translations.sourceUploadTitle}
135
- targetPlaceholder={translations.targetUploadTitle}
136
- layout="horizontal"
137
- />
138
- </View>
139
- </AIGenerationForm>
140
- </ScrollView>
141
-
142
- {renderProcessingModal
143
- ? renderProcessingModal({ visible: feature.isProcessing, progress: feature.progress })
144
- : defaultModal}
145
- </>
61
+ <DualImageVideoFeatureLayout
62
+ feature={feature}
63
+ translations={translations}
64
+ modalTranslations={modalTranslations}
65
+ renderProcessingModal={renderProcessingModal}
66
+ renderInput={({ sourceImageUri, targetImageUri, onSelectSource, onSelectTarget, isDisabled }) => (
67
+ <View style={styles.pickerContainer}>
68
+ <DualImagePicker
69
+ sourceImageUri={sourceImageUri}
70
+ targetImageUri={targetImageUri}
71
+ isDisabled={isDisabled}
72
+ onSelectSource={onSelectSource}
73
+ onSelectTarget={onSelectTarget}
74
+ sourcePlaceholder={translations.sourceUploadTitle}
75
+ targetPlaceholder={translations.targetUploadTitle}
76
+ layout="horizontal"
77
+ />
78
+ </View>
79
+ )}
80
+ />
146
81
  );
147
82
  };
148
83
 
149
84
  const styles = StyleSheet.create({
150
- container: {
151
- flex: 1,
152
- },
153
- content: {
154
- paddingVertical: 16,
155
- },
156
85
  pickerContainer: {
157
86
  marginHorizontal: 16,
158
87
  marginBottom: 16,
@@ -1,16 +1,14 @@
1
1
  /**
2
2
  * AIKissFeature Component
3
3
  * Self-contained AI kiss video feature UI component
4
- * Uses hook internally, only requires config and translations
4
+ * Uses centralized DualImageVideoFeatureLayout for consistent UX
5
5
  */
6
6
 
7
- import React, { useCallback } from "react";
8
- import { View, ScrollView, StyleSheet } from "react-native";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
7
+ import React, { useMemo } from "react";
8
+ import { View, StyleSheet } from "react-native";
10
9
  import { DualImagePicker } from "../../../../presentation/components/image-picker/DualImagePicker";
11
- import { AIGenerationForm } from "../../../../presentation/components/AIGenerationForm";
12
- import { AIGenerationResult } from "../../../../presentation/components/display/AIGenerationResult";
13
- import { GenerationProgressModal } from "../../../../presentation/components/GenerationProgressModal";
10
+ import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
11
+ import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
14
12
  import { useAIKissFeature } from "../hooks";
15
13
  import type {
16
14
  AIKissTranslations,
@@ -19,16 +17,17 @@ import type {
19
17
 
20
18
  export interface AIKissFeatureProps {
21
19
  config: AIKissFeatureConfig;
22
- translations: AIKissTranslations;
20
+ translations: AIKissTranslations & {
21
+ modalTitle?: string;
22
+ modalMessage?: string;
23
+ modalHint?: string;
24
+ modalBackgroundHint?: string;
25
+ };
23
26
  onSelectSourceImage: () => Promise<string | null>;
24
27
  onSelectTargetImage: () => Promise<string | null>;
25
28
  onSaveVideo: (videoUrl: string) => Promise<void>;
26
- /** Called before processing starts. Return false to cancel. */
27
29
  onBeforeProcess?: () => Promise<boolean>;
28
- renderProcessingModal?: (props: {
29
- visible: boolean;
30
- progress: number;
31
- }) => React.ReactNode;
30
+ renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
32
31
  }
33
32
 
34
33
  export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
@@ -40,8 +39,6 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
40
39
  onBeforeProcess,
41
40
  renderProcessingModal,
42
41
  }) => {
43
- const tokens = useAppDesignTokens();
44
-
45
42
  const feature = useAIKissFeature({
46
43
  config,
47
44
  onSelectSourceImage,
@@ -50,103 +47,41 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
50
47
  onBeforeProcess,
51
48
  });
52
49
 
53
- const handleProcess = useCallback(() => {
54
- void feature.process();
55
- }, [feature]);
56
-
57
- const handleSave = useCallback(() => {
58
- void feature.save();
59
- }, [feature]);
60
-
61
- const handleSelectSource = useCallback(() => {
62
- void feature.selectSourceImage();
63
- }, [feature]);
64
-
65
- const handleSelectTarget = useCallback(() => {
66
- void feature.selectTargetImage();
67
- }, [feature]);
68
-
69
- if (feature.processedVideoUrl) {
70
- return (
71
- <ScrollView
72
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
73
- contentContainerStyle={styles.content}
74
- showsVerticalScrollIndicator={false}
75
- >
76
- <AIGenerationResult
77
- successText={translations.successText}
78
- primaryAction={{
79
- label: translations.saveButtonText,
80
- onPress: handleSave,
81
- }}
82
- secondaryAction={{
83
- label: translations.tryAnotherText,
84
- onPress: feature.reset,
85
- }}
86
- />
87
- </ScrollView>
88
- );
89
- }
90
-
91
- const defaultModal = (
92
- <GenerationProgressModal
93
- visible={feature.isProcessing}
94
- progress={feature.progress}
95
- icon="sparkles"
96
- title={translations.modalTitle || "Creating your video"}
97
- message={translations.modalMessage || "AI is working its magic..."}
98
- hint={translations.modalHint || "This may take a moment"}
99
- backgroundHint={translations.modalBackgroundHint || "Continue in background"}
100
- onClose={() => {
101
- // Allow continuing in background - just close modal
102
- }}
103
- />
50
+ const modalTranslations = useMemo(
51
+ () => ({
52
+ title: translations.modalTitle || "Creating your video",
53
+ message: translations.modalMessage || "AI is working its magic...",
54
+ hint: translations.modalHint || "This may take a moment",
55
+ backgroundHint: translations.modalBackgroundHint || "Continue in background",
56
+ }),
57
+ [translations],
104
58
  );
105
59
 
106
60
  return (
107
- <>
108
- <ScrollView
109
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
110
- contentContainerStyle={styles.content}
111
- showsVerticalScrollIndicator={false}
112
- >
113
- <AIGenerationForm
114
- onGenerate={handleProcess}
115
- isGenerating={feature.isProcessing}
116
- translations={{
117
- generateButton: translations.processButtonText,
118
- generatingButton: translations.processingText,
119
- }}
120
- >
121
- <View style={styles.pickerContainer}>
122
- <DualImagePicker
123
- sourceImageUri={feature.sourceImageUri}
124
- targetImageUri={feature.targetImageUri}
125
- isDisabled={feature.isProcessing}
126
- onSelectSource={handleSelectSource}
127
- onSelectTarget={handleSelectTarget}
128
- sourcePlaceholder={translations.sourceUploadTitle}
129
- targetPlaceholder={translations.targetUploadTitle}
130
- layout="horizontal"
131
- />
132
- </View>
133
- </AIGenerationForm>
134
- </ScrollView>
135
-
136
- {renderProcessingModal
137
- ? renderProcessingModal({ visible: feature.isProcessing, progress: feature.progress })
138
- : defaultModal}
139
- </>
61
+ <DualImageVideoFeatureLayout
62
+ feature={feature}
63
+ translations={translations}
64
+ modalTranslations={modalTranslations}
65
+ renderProcessingModal={renderProcessingModal}
66
+ renderInput={({ sourceImageUri, targetImageUri, onSelectSource, onSelectTarget, isDisabled }) => (
67
+ <View style={styles.pickerContainer}>
68
+ <DualImagePicker
69
+ sourceImageUri={sourceImageUri}
70
+ targetImageUri={targetImageUri}
71
+ isDisabled={isDisabled}
72
+ onSelectSource={onSelectSource}
73
+ onSelectTarget={onSelectTarget}
74
+ sourcePlaceholder={translations.sourceUploadTitle}
75
+ targetPlaceholder={translations.targetUploadTitle}
76
+ layout="horizontal"
77
+ />
78
+ </View>
79
+ )}
80
+ />
140
81
  );
141
82
  };
142
83
 
143
84
  const styles = StyleSheet.create({
144
- container: {
145
- flex: 1,
146
- },
147
- content: {
148
- paddingVertical: 16,
149
- },
150
85
  pickerContainer: {
151
86
  marginHorizontal: 16,
152
87
  marginBottom: 16,
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * AnimeSelfieFeature Component
3
3
  * Self-contained anime selfie feature UI component
4
- * Uses hook internally, only requires config and translations
4
+ * Uses centralized SingleImageFeatureLayout for consistent UX
5
5
  */
6
6
 
7
- import React, { useCallback, useMemo } from "react";
8
- import { ScrollView, StyleSheet, Image } from "react-native";
9
- import { useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
7
+ import React, { useMemo } from "react";
8
+ import { Image, StyleSheet } from "react-native";
10
9
  import { PhotoUploadCard } from "../../../../presentation/components/PhotoUploadCard";
11
- import { AIGenerationForm } from "../../../../presentation/components/AIGenerationForm";
12
- import { AIGenerationResult } from "../../../../presentation/components/display/AIGenerationResult";
10
+ import { SingleImageFeatureLayout } from "../../../../presentation/layouts";
11
+ import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
13
12
  import { useAnimeSelfieFeature } from "../hooks";
14
13
  import type {
15
14
  AnimeSelfieTranslations,
@@ -18,15 +17,17 @@ import type {
18
17
 
19
18
  export interface AnimeSelfieFeatureProps {
20
19
  config: AnimeSelfieFeatureConfig;
21
- translations: AnimeSelfieTranslations;
20
+ translations: AnimeSelfieTranslations & {
21
+ modalTitle?: string;
22
+ modalMessage?: string;
23
+ modalHint?: string;
24
+ modalBackgroundHint?: string;
25
+ };
22
26
  onSelectImage: () => Promise<string | null>;
23
27
  onSaveImage: (imageUrl: string) => Promise<void>;
24
28
  /** Called before processing starts. Return false to cancel. */
25
29
  onBeforeProcess?: () => Promise<boolean>;
26
- renderProcessingModal?: (props: {
27
- visible: boolean;
28
- progress: number;
29
- }) => React.ReactNode;
30
+ renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
30
31
  }
31
32
 
32
33
  export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
@@ -37,10 +38,6 @@ export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
37
38
  onBeforeProcess,
38
39
  renderProcessingModal,
39
40
  }) => {
40
- const tokens = useAppDesignTokens();
41
- const { width: screenWidth, horizontalPadding } = useResponsive();
42
- const imageSize = screenWidth - horizontalPadding * 2;
43
-
44
41
  const feature = useAnimeSelfieFeature({
45
42
  config,
46
43
  onSelectImage,
@@ -48,125 +45,55 @@ export const AnimeSelfieFeature: React.FC<AnimeSelfieFeatureProps> = ({
48
45
  onBeforeProcess,
49
46
  });
50
47
 
51
- const photoTranslations = useMemo(
48
+ const modalTranslations = useMemo(
52
49
  () => ({
53
- tapToUpload: translations.uploadTitle,
54
- selectPhoto: translations.uploadSubtitle,
55
- change: translations.uploadChange,
56
- analyzing: translations.uploadAnalyzing,
50
+ title: translations.modalTitle || "Processing",
51
+ message: translations.modalMessage || "AI is transforming your photo...",
52
+ hint: translations.modalHint || "This may take a moment",
53
+ backgroundHint: translations.modalBackgroundHint || "Continue in background",
57
54
  }),
58
55
  [translations],
59
56
  );
60
57
 
61
- const handleProcess = useCallback(() => {
62
- void feature.process();
63
- }, [feature]);
64
-
65
- const handleSave = useCallback(() => {
66
- void feature.save();
67
- }, [feature]);
68
-
69
- const handleSelectImage = useCallback(() => {
70
- void feature.selectImage();
71
- }, [feature]);
72
-
73
- if (feature.processedUrl) {
74
- return (
75
- <ScrollView
76
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
77
- contentContainerStyle={styles.content}
78
- showsVerticalScrollIndicator={false}
79
- >
80
- <AIGenerationResult
81
- successText={translations.successText}
82
- primaryAction={{
83
- label: translations.saveButtonText,
84
- onPress: handleSave,
85
- }}
86
- secondaryAction={{
87
- label: translations.tryAnotherText,
88
- onPress: feature.reset,
89
- }}
90
- >
91
- <Image
92
- source={{ uri: feature.processedUrl }}
93
- style={[styles.resultImage, { width: imageSize, height: imageSize }]}
94
- resizeMode="contain"
95
- />
96
- </AIGenerationResult>
97
- </ScrollView>
98
- );
99
- }
100
-
101
58
  return (
102
- <>
103
- <ScrollView
104
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
105
- contentContainerStyle={styles.content}
106
- showsVerticalScrollIndicator={false}
107
- >
108
- <AIGenerationForm
109
- onGenerate={handleProcess}
110
- isGenerating={feature.isProcessing}
111
- progress={feature.progress}
59
+ <SingleImageFeatureLayout
60
+ feature={feature}
61
+ translations={translations}
62
+ modalTranslations={modalTranslations}
63
+ renderProcessingModal={renderProcessingModal}
64
+ renderInput={({ imageUri, onSelect, isDisabled, isProcessing }) => (
65
+ <PhotoUploadCard
66
+ imageUri={imageUri}
67
+ onPress={onSelect}
68
+ isValidating={isProcessing}
69
+ disabled={isDisabled}
112
70
  translations={{
113
- generateButton: translations.processButtonText,
114
- generatingButton: translations.processingText,
115
- progressTitle: translations.processingText,
71
+ tapToUpload: translations.uploadTitle,
72
+ selectPhoto: translations.uploadSubtitle,
73
+ change: translations.uploadChange,
74
+ analyzing: translations.uploadAnalyzing,
116
75
  }}
117
- >
118
- <PhotoUploadCard
119
- imageUri={feature.imageUri}
120
- onPress={handleSelectImage}
121
- isValidating={feature.isProcessing}
122
- disabled={feature.isProcessing}
123
- translations={photoTranslations}
124
- config={{
125
- aspectRatio: 1,
126
- borderRadius: 24,
127
- showValidationStatus: false,
128
- allowChange: true,
129
- }}
130
- />
131
- </AIGenerationForm>
132
- </ScrollView>
133
-
134
- {renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
135
- </>
76
+ config={{
77
+ aspectRatio: 1,
78
+ borderRadius: 24,
79
+ showValidationStatus: false,
80
+ allowChange: true,
81
+ }}
82
+ />
83
+ )}
84
+ renderResult={({ imageUrl, imageSize }) => (
85
+ <Image
86
+ source={{ uri: imageUrl }}
87
+ style={[styles.resultImage, { width: imageSize, height: imageSize }]}
88
+ resizeMode="contain"
89
+ />
90
+ )}
91
+ />
136
92
  );
137
93
  };
138
94
 
139
95
  const styles = StyleSheet.create({
140
- container: {
141
- flex: 1,
142
- },
143
- content: {
144
- paddingVertical: 16,
145
- },
146
- description: {
147
- textAlign: "center",
148
- marginHorizontal: 24,
149
- marginBottom: 24,
150
- lineHeight: 24,
151
- },
152
- successText: {
153
- textAlign: "center",
154
- marginBottom: 24,
155
- },
156
- resultImageContainer: {
157
- alignItems: "center",
158
- marginHorizontal: 24,
159
- marginBottom: 24,
160
- },
161
96
  resultImage: {
162
97
  borderRadius: 16,
163
98
  },
164
- resultActions: {
165
- marginHorizontal: 24,
166
- gap: 12,
167
- },
168
- buttonContainer: {
169
- marginHorizontal: 24,
170
- marginTop: 8,
171
- },
172
99
  });