@umituz/react-native-ai-generation-content 1.17.181 → 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.181",
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,15 +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";
10
+ import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
11
+ import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
13
12
  import { useAIHugFeature } from "../hooks";
14
13
  import type {
15
14
  AIHugTranslations,
@@ -17,23 +16,18 @@ import type {
17
16
  } from "../../domain/types";
18
17
 
19
18
  export interface AIHugFeatureProps {
20
- /** Feature configuration with provider-specific settings */
21
19
  config: AIHugFeatureConfig;
22
- /** Translations for all UI text */
23
- translations: AIHugTranslations;
24
- /** Source image picker callback */
20
+ translations: AIHugTranslations & {
21
+ modalTitle?: string;
22
+ modalMessage?: string;
23
+ modalHint?: string;
24
+ modalBackgroundHint?: string;
25
+ };
25
26
  onSelectSourceImage: () => Promise<string | null>;
26
- /** Target image picker callback */
27
27
  onSelectTargetImage: () => Promise<string | null>;
28
- /** Save video callback */
29
28
  onSaveVideo: (videoUrl: string) => Promise<void>;
30
- /** Called before processing starts. Return false to cancel. */
31
29
  onBeforeProcess?: () => Promise<boolean>;
32
- /** Optional custom processing modal renderer */
33
- renderProcessingModal?: (props: {
34
- visible: boolean;
35
- progress: number;
36
- }) => React.ReactNode;
30
+ renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
37
31
  }
38
32
 
39
33
  export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
@@ -45,8 +39,6 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
45
39
  onBeforeProcess,
46
40
  renderProcessingModal,
47
41
  }) => {
48
- const tokens = useAppDesignTokens();
49
-
50
42
  const feature = useAIHugFeature({
51
43
  config,
52
44
  onSelectSourceImage,
@@ -55,86 +47,41 @@ export const AIHugFeature: React.FC<AIHugFeatureProps> = ({
55
47
  onBeforeProcess,
56
48
  });
57
49
 
58
- const handleProcess = useCallback(() => {
59
- void feature.process();
60
- }, [feature]);
61
-
62
- const handleSave = useCallback(() => {
63
- void feature.save();
64
- }, [feature]);
65
-
66
- const handleSelectSource = useCallback(() => {
67
- void feature.selectSourceImage();
68
- }, [feature]);
69
-
70
- const handleSelectTarget = useCallback(() => {
71
- void feature.selectTargetImage();
72
- }, [feature]);
73
-
74
- if (feature.processedVideoUrl) {
75
- return (
76
- <ScrollView
77
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
78
- contentContainerStyle={styles.content}
79
- showsVerticalScrollIndicator={false}
80
- >
81
- <AIGenerationResult
82
- successText={translations.successText}
83
- primaryAction={{
84
- label: translations.saveButtonText,
85
- onPress: handleSave,
86
- }}
87
- secondaryAction={{
88
- label: translations.tryAnotherText,
89
- onPress: feature.reset,
90
- }}
91
- />
92
- </ScrollView>
93
- );
94
- }
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],
58
+ );
95
59
 
96
60
  return (
97
- <>
98
- <ScrollView
99
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
100
- contentContainerStyle={styles.content}
101
- showsVerticalScrollIndicator={false}
102
- >
103
- <AIGenerationForm
104
- onGenerate={handleProcess}
105
- isGenerating={feature.isProcessing}
106
- translations={{
107
- generateButton: translations.processButtonText,
108
- generatingButton: translations.processingText,
109
- }}
110
- >
111
- <View style={styles.pickerContainer}>
112
- <DualImagePicker
113
- sourceImageUri={feature.sourceImageUri}
114
- targetImageUri={feature.targetImageUri}
115
- isDisabled={feature.isProcessing}
116
- onSelectSource={handleSelectSource}
117
- onSelectTarget={handleSelectTarget}
118
- sourcePlaceholder={translations.sourceUploadTitle}
119
- targetPlaceholder={translations.targetUploadTitle}
120
- layout="horizontal"
121
- />
122
- </View>
123
- </AIGenerationForm>
124
- </ScrollView>
125
-
126
- {renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
127
- </>
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
+ />
128
81
  );
129
82
  };
130
83
 
131
84
  const styles = StyleSheet.create({
132
- container: {
133
- flex: 1,
134
- },
135
- content: {
136
- paddingVertical: 16,
137
- },
138
85
  pickerContainer: {
139
86
  marginHorizontal: 16,
140
87
  marginBottom: 16,
@@ -1,15 +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";
10
+ import { DualImageVideoFeatureLayout } from "../../../../presentation/layouts";
11
+ import type { ProcessingModalRenderProps } from "../../../../presentation/layouts";
13
12
  import { useAIKissFeature } from "../hooks";
14
13
  import type {
15
14
  AIKissTranslations,
@@ -18,16 +17,17 @@ import type {
18
17
 
19
18
  export interface AIKissFeatureProps {
20
19
  config: AIKissFeatureConfig;
21
- translations: AIKissTranslations;
20
+ translations: AIKissTranslations & {
21
+ modalTitle?: string;
22
+ modalMessage?: string;
23
+ modalHint?: string;
24
+ modalBackgroundHint?: string;
25
+ };
22
26
  onSelectSourceImage: () => Promise<string | null>;
23
27
  onSelectTargetImage: () => Promise<string | null>;
24
28
  onSaveVideo: (videoUrl: string) => Promise<void>;
25
- /** Called before processing starts. Return false to cancel. */
26
29
  onBeforeProcess?: () => Promise<boolean>;
27
- renderProcessingModal?: (props: {
28
- visible: boolean;
29
- progress: number;
30
- }) => React.ReactNode;
30
+ renderProcessingModal?: (props: ProcessingModalRenderProps) => React.ReactNode;
31
31
  }
32
32
 
33
33
  export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
@@ -39,8 +39,6 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
39
39
  onBeforeProcess,
40
40
  renderProcessingModal,
41
41
  }) => {
42
- const tokens = useAppDesignTokens();
43
-
44
42
  const feature = useAIKissFeature({
45
43
  config,
46
44
  onSelectSourceImage,
@@ -49,86 +47,41 @@ export const AIKissFeature: React.FC<AIKissFeatureProps> = ({
49
47
  onBeforeProcess,
50
48
  });
51
49
 
52
- const handleProcess = useCallback(() => {
53
- void feature.process();
54
- }, [feature]);
55
-
56
- const handleSave = useCallback(() => {
57
- void feature.save();
58
- }, [feature]);
59
-
60
- const handleSelectSource = useCallback(() => {
61
- void feature.selectSourceImage();
62
- }, [feature]);
63
-
64
- const handleSelectTarget = useCallback(() => {
65
- void feature.selectTargetImage();
66
- }, [feature]);
67
-
68
- if (feature.processedVideoUrl) {
69
- return (
70
- <ScrollView
71
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
72
- contentContainerStyle={styles.content}
73
- showsVerticalScrollIndicator={false}
74
- >
75
- <AIGenerationResult
76
- successText={translations.successText}
77
- primaryAction={{
78
- label: translations.saveButtonText,
79
- onPress: handleSave,
80
- }}
81
- secondaryAction={{
82
- label: translations.tryAnotherText,
83
- onPress: feature.reset,
84
- }}
85
- />
86
- </ScrollView>
87
- );
88
- }
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],
58
+ );
89
59
 
90
60
  return (
91
- <>
92
- <ScrollView
93
- style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
94
- contentContainerStyle={styles.content}
95
- showsVerticalScrollIndicator={false}
96
- >
97
- <AIGenerationForm
98
- onGenerate={handleProcess}
99
- isGenerating={feature.isProcessing}
100
- translations={{
101
- generateButton: translations.processButtonText,
102
- generatingButton: translations.processingText,
103
- }}
104
- >
105
- <View style={styles.pickerContainer}>
106
- <DualImagePicker
107
- sourceImageUri={feature.sourceImageUri}
108
- targetImageUri={feature.targetImageUri}
109
- isDisabled={feature.isProcessing}
110
- onSelectSource={handleSelectSource}
111
- onSelectTarget={handleSelectTarget}
112
- sourcePlaceholder={translations.sourceUploadTitle}
113
- targetPlaceholder={translations.targetUploadTitle}
114
- layout="horizontal"
115
- />
116
- </View>
117
- </AIGenerationForm>
118
- </ScrollView>
119
-
120
- {renderProcessingModal?.({ visible: feature.isProcessing, progress: feature.progress })}
121
- </>
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
+ />
122
81
  );
123
82
  };
124
83
 
125
84
  const styles = StyleSheet.create({
126
- container: {
127
- flex: 1,
128
- },
129
- content: {
130
- paddingVertical: 16,
131
- },
132
85
  pickerContainer: {
133
86
  marginHorizontal: 16,
134
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
  });