@umituz/react-native-ai-generation-content 1.17.265 → 1.17.266

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.265",
3
+ "version": "1.17.266",
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",
@@ -77,6 +77,7 @@
77
77
  "expo-haptics": "^15.0.8",
78
78
  "expo-image": "^3.0.11",
79
79
  "expo-image-manipulator": "^14.0.8",
80
+ "expo-image-picker": "^17.0.10",
80
81
  "expo-linear-gradient": "^15.0.8",
81
82
  "expo-localization": "^17.0.8",
82
83
  "expo-media-library": "^18.2.1",
@@ -3,7 +3,7 @@
3
3
  * Handles persistence of partner profile data
4
4
  */
5
5
 
6
- import { storageRepository } from "@umituz/react-native-design-system";
6
+ import AsyncStorage from "@react-native-async-storage/async-storage";
7
7
  import { PartnerProfile } from "../../domain/types";
8
8
 
9
9
  const PARTNER_PROFILE_STORAGE_KEY = "love_message_partner_profile";
@@ -14,12 +14,9 @@ export const PartnerProfileRepository = {
14
14
  */
15
15
  getProfile: async (): Promise<PartnerProfile | null> => {
16
16
  try {
17
- const result = await storageRepository.getItem(
18
- PARTNER_PROFILE_STORAGE_KEY,
19
- "",
20
- );
21
- if (result.success && result.data) {
22
- return JSON.parse(result.data) as PartnerProfile;
17
+ const data = await AsyncStorage.getItem(PARTNER_PROFILE_STORAGE_KEY);
18
+ if (data) {
19
+ return JSON.parse(data) as PartnerProfile;
23
20
  }
24
21
  return null;
25
22
  } catch {
@@ -32,11 +29,8 @@ export const PartnerProfileRepository = {
32
29
  */
33
30
  saveProfile: async (profile: PartnerProfile): Promise<boolean> => {
34
31
  try {
35
- const result = await storageRepository.setItem(
36
- PARTNER_PROFILE_STORAGE_KEY,
37
- JSON.stringify(profile),
38
- );
39
- return result.success;
32
+ await AsyncStorage.setItem(PARTNER_PROFILE_STORAGE_KEY, JSON.stringify(profile));
33
+ return true;
40
34
  } catch {
41
35
  return false;
42
36
  }
@@ -47,10 +41,8 @@ export const PartnerProfileRepository = {
47
41
  */
48
42
  clearProfile: async (): Promise<boolean> => {
49
43
  try {
50
- const result = await storageRepository.removeItem(
51
- PARTNER_PROFILE_STORAGE_KEY,
52
- );
53
- return result.success;
44
+ await AsyncStorage.removeItem(PARTNER_PROFILE_STORAGE_KEY);
45
+ return true;
54
46
  } catch {
55
47
  return false;
56
48
  }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Partner Upload Types
3
+ * Generic partner/photo upload feature types
4
+ */
5
+
6
+ export interface UploadedImage {
7
+ readonly uri: string;
8
+ readonly base64?: string;
9
+ readonly previewUrl: string;
10
+ readonly width?: number;
11
+ readonly height?: number;
12
+ }
13
+
14
+ export interface PartnerStepConfig {
15
+ readonly titleKey: string;
16
+ readonly subtitleKey: string;
17
+ readonly showFaceDetection?: boolean;
18
+ readonly showNameInput?: boolean;
19
+ readonly showPhotoTips?: boolean;
20
+ readonly maxNameLength?: number;
21
+ readonly namePlaceholderKey?: string;
22
+ }
23
+
24
+ export interface PartnerStepTranslations {
25
+ readonly tapToUpload: string;
26
+ readonly selectPhoto: string;
27
+ readonly change: string;
28
+ readonly analyzing: string;
29
+ readonly continue: string;
30
+ readonly faceDetectionLabel?: string;
31
+ readonly namePlaceholder?: string;
32
+ readonly photoTip1?: string;
33
+ readonly photoTip2?: string;
34
+ readonly photoTip3?: string;
35
+ }
36
+
37
+ export interface PhotoTipsConfig {
38
+ readonly tips: readonly string[];
39
+ readonly icon?: string;
40
+ }
41
+
42
+ export const DEFAULT_PARTNER_STEP_CONFIG: PartnerStepConfig = {
43
+ titleKey: "partner.upload.title",
44
+ subtitleKey: "partner.upload.subtitle",
45
+ showFaceDetection: false,
46
+ showNameInput: true,
47
+ showPhotoTips: true,
48
+ maxNameLength: 50,
49
+ namePlaceholderKey: "partner.upload.namePlaceholder",
50
+ } as const;
51
+
52
+ export const DEFAULT_PHOTO_TIPS: PhotoTipsConfig = {
53
+ tips: [
54
+ "photoTips.tip1",
55
+ "photoTips.tip2",
56
+ "photoTips.tip3",
57
+ ],
58
+ icon: "lightbulb",
59
+ } as const;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Partner Upload Feature
3
+ * Generic partner/photo upload screens and components
4
+ */
5
+
6
+ export type {
7
+ UploadedImage,
8
+ PartnerStepConfig,
9
+ PartnerStepTranslations,
10
+ PhotoTipsConfig,
11
+ } from "./domain/types";
12
+ export { DEFAULT_PARTNER_STEP_CONFIG, DEFAULT_PHOTO_TIPS } from "./domain/types";
13
+
14
+ export { PhotoTips, PartnerInfoInput } from "./presentation/components";
15
+ export type { PhotoTipsProps, PhotoTipConfig, PartnerInfoInputProps } from "./presentation/components";
16
+
17
+ export { usePartnerStep } from "./presentation/hooks";
18
+ export type {
19
+ UsePartnerStepConfig,
20
+ UsePartnerStepTranslations,
21
+ UsePartnerStepOptions,
22
+ UsePartnerStepReturn,
23
+ } from "./presentation/hooks";
24
+
25
+ export { PartnerStepScreen } from "./presentation/screens";
26
+ export type {
27
+ PartnerStepScreenProps,
28
+ PartnerStepScreenTranslations,
29
+ PartnerStepScreenConfig,
30
+ } from "./presentation/screens";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * PartnerInfoInput Component
3
+ * Name and optional description input for partner
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicInput, AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+
10
+ export interface PartnerInfoInputProps {
11
+ readonly t: (key: string) => string;
12
+ readonly name: string;
13
+ readonly onNameChange: (name: string) => void;
14
+ readonly description?: string;
15
+ readonly onDescriptionChange?: (description: string) => void;
16
+ readonly showName?: boolean;
17
+ readonly showDescription?: boolean;
18
+ readonly maxNameLength?: number;
19
+ readonly maxDescriptionLength?: number;
20
+ readonly nameLabelKey?: string;
21
+ readonly namePlaceholderKey?: string;
22
+ readonly descriptionLabelKey?: string;
23
+ readonly descriptionPlaceholderKey?: string;
24
+ readonly optionalKey?: string;
25
+ }
26
+
27
+ export const PartnerInfoInput: React.FC<PartnerInfoInputProps> = ({
28
+ t,
29
+ name,
30
+ onNameChange,
31
+ description = "",
32
+ onDescriptionChange,
33
+ showName = false,
34
+ showDescription = false,
35
+ maxNameLength = 30,
36
+ maxDescriptionLength = 200,
37
+ nameLabelKey = "photoUpload.nameLabel",
38
+ namePlaceholderKey = "photoUpload.namePlaceholder",
39
+ descriptionLabelKey = "photoUpload.descriptionLabel",
40
+ descriptionPlaceholderKey = "photoUpload.descriptionPlaceholder",
41
+ optionalKey = "common.optional",
42
+ }) => {
43
+ const tokens = useAppDesignTokens();
44
+
45
+ const styles = useMemo(
46
+ () =>
47
+ StyleSheet.create({
48
+ container: {
49
+ paddingHorizontal: 24,
50
+ gap: 16,
51
+ },
52
+ optionalLabel: {
53
+ flexDirection: "row",
54
+ alignItems: "center",
55
+ gap: 8,
56
+ marginBottom: 8,
57
+ },
58
+ optional: {
59
+ fontSize: 12,
60
+ color: tokens.colors.textTertiary,
61
+ fontStyle: "italic",
62
+ },
63
+ label: {
64
+ fontSize: 14,
65
+ fontWeight: "600",
66
+ color: tokens.colors.textSecondary,
67
+ },
68
+ }),
69
+ [tokens],
70
+ );
71
+
72
+ if (!showName && !showDescription) {
73
+ return null;
74
+ }
75
+
76
+ return (
77
+ <View style={styles.container}>
78
+ {showName && (
79
+ <AtomicInput
80
+ label={t(nameLabelKey)}
81
+ value={name}
82
+ onChangeText={onNameChange}
83
+ placeholder={t(namePlaceholderKey)}
84
+ maxLength={maxNameLength}
85
+ showCharacterCount
86
+ variant="outlined"
87
+ size="md"
88
+ />
89
+ )}
90
+
91
+ {showDescription && onDescriptionChange && (
92
+ <View>
93
+ <View style={styles.optionalLabel}>
94
+ <AtomicText style={styles.label}>{t(descriptionLabelKey)}</AtomicText>
95
+ <AtomicText style={styles.optional}>({t(optionalKey)})</AtomicText>
96
+ </View>
97
+ <AtomicInput
98
+ value={description}
99
+ onChangeText={onDescriptionChange}
100
+ placeholder={t(descriptionPlaceholderKey)}
101
+ multiline
102
+ numberOfLines={3}
103
+ maxLength={maxDescriptionLength}
104
+ showCharacterCount
105
+ variant="outlined"
106
+ inputStyle={{ minHeight: 100, textAlignVertical: "top" }}
107
+ />
108
+ </View>
109
+ )}
110
+ </View>
111
+ );
112
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * PhotoTips Component
3
+ * Displays photo upload tips in a grid layout
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { InfoGrid, type InfoGridItem } from "@umituz/react-native-design-system";
8
+
9
+ export interface PhotoTipConfig {
10
+ readonly icon: string;
11
+ readonly textKey: string;
12
+ }
13
+
14
+ export interface PhotoTipsProps {
15
+ readonly t: (key: string) => string;
16
+ readonly titleKey?: string;
17
+ readonly headerIcon?: string;
18
+ readonly tips?: readonly PhotoTipConfig[];
19
+ readonly style?: object;
20
+ }
21
+
22
+ const DEFAULT_TIPS: readonly PhotoTipConfig[] = [
23
+ { icon: "sunny-outline", textKey: "photoUpload.tips.lighting" },
24
+ { icon: "person-outline", textKey: "photoUpload.tips.faceForward" },
25
+ { icon: "eye-outline", textKey: "photoUpload.tips.clearFace" },
26
+ { icon: "diamond-outline", textKey: "photoUpload.tips.goodQuality" },
27
+ ] as const;
28
+
29
+ export const PhotoTips: React.FC<PhotoTipsProps> = ({
30
+ t,
31
+ titleKey = "photoUpload.tips.title",
32
+ headerIcon = "bulb",
33
+ tips = DEFAULT_TIPS,
34
+ style,
35
+ }) => {
36
+ const gridItems: InfoGridItem[] = useMemo(
37
+ () =>
38
+ tips.map((tip) => ({
39
+ icon: tip.icon,
40
+ text: t(tip.textKey),
41
+ })),
42
+ [tips, t],
43
+ );
44
+
45
+ return (
46
+ <InfoGrid
47
+ title={t(titleKey)}
48
+ headerIcon={headerIcon}
49
+ items={gridItems}
50
+ style={style ?? { marginHorizontal: 24, marginBottom: 20 }}
51
+ />
52
+ );
53
+ };
@@ -0,0 +1,4 @@
1
+ export { PhotoTips } from "./PhotoTips";
2
+ export type { PhotoTipsProps, PhotoTipConfig } from "./PhotoTips";
3
+ export { PartnerInfoInput } from "./PartnerInfoInput";
4
+ export type { PartnerInfoInputProps } from "./PartnerInfoInput";
@@ -0,0 +1,7 @@
1
+ export { usePartnerStep } from "./usePartnerStep";
2
+ export type {
3
+ UsePartnerStepConfig,
4
+ UsePartnerStepTranslations,
5
+ UsePartnerStepOptions,
6
+ UsePartnerStepReturn,
7
+ } from "./usePartnerStep";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * usePartnerStep Hook
3
+ * Manages partner photo upload step logic
4
+ */
5
+
6
+ import { useState, useCallback } from "react";
7
+ import { Alert } from "react-native";
8
+ import * as ImagePicker from "expo-image-picker";
9
+ import * as FileSystem from "expo-file-system";
10
+ import type { UploadedImage } from "../../domain/types";
11
+
12
+ export interface UsePartnerStepConfig {
13
+ readonly maxFileSizeMB?: number;
14
+ readonly imageQuality?: number;
15
+ readonly allowsEditing?: boolean;
16
+ }
17
+
18
+ export interface UsePartnerStepTranslations {
19
+ readonly fileTooLarge: string;
20
+ readonly maxFileSize: string;
21
+ readonly error: string;
22
+ readonly uploadFailed: string;
23
+ }
24
+
25
+ export interface UsePartnerStepOptions {
26
+ readonly initialName?: string;
27
+ readonly config?: UsePartnerStepConfig;
28
+ readonly translations: UsePartnerStepTranslations;
29
+ }
30
+
31
+ interface PartnerStepState {
32
+ image: UploadedImage | null;
33
+ name: string;
34
+ description: string;
35
+ }
36
+
37
+ const DEFAULT_CONFIG: UsePartnerStepConfig = {
38
+ maxFileSizeMB: 10,
39
+ imageQuality: 0.7,
40
+ allowsEditing: true,
41
+ };
42
+
43
+ export const usePartnerStep = (options: UsePartnerStepOptions) => {
44
+ const { initialName = "", config = DEFAULT_CONFIG, translations } = options;
45
+
46
+ const [state, setState] = useState<PartnerStepState>({
47
+ image: null,
48
+ name: initialName,
49
+ description: "",
50
+ });
51
+
52
+ const setName = useCallback((name: string) => {
53
+ setState((prev) => ({ ...prev, name }));
54
+ }, []);
55
+
56
+ const setDescription = useCallback((description: string) => {
57
+ setState((prev) => ({ ...prev, description }));
58
+ }, []);
59
+
60
+ const handlePickImage = useCallback(async () => {
61
+ try {
62
+ const maxFileSizeMB = config.maxFileSizeMB ?? 10;
63
+ const result = await ImagePicker.launchImageLibraryAsync({
64
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
65
+ allowsEditing: config.allowsEditing ?? true,
66
+ quality: config.imageQuality ?? 0.7,
67
+ });
68
+
69
+ if (result.canceled || !result.assets?.[0]) return;
70
+
71
+ const asset = result.assets[0];
72
+
73
+ if (asset.fileSize && asset.fileSize > maxFileSizeMB * 1024 * 1024) {
74
+ Alert.alert(
75
+ translations.fileTooLarge,
76
+ translations.maxFileSize.replace("{size}", String(maxFileSizeMB)),
77
+ );
78
+ return;
79
+ }
80
+
81
+ const base64 = await FileSystem.readAsStringAsync(asset.uri, {
82
+ encoding: "base64",
83
+ });
84
+
85
+ const uploadedImage: UploadedImage = {
86
+ uri: asset.uri,
87
+ previewUrl: asset.uri,
88
+ base64: `data:image/jpeg;base64,${base64}`,
89
+ width: asset.width,
90
+ height: asset.height,
91
+ };
92
+
93
+ setState((prev) => ({
94
+ ...prev,
95
+ image: uploadedImage,
96
+ }));
97
+ } catch {
98
+ Alert.alert(translations.error, translations.uploadFailed);
99
+ }
100
+ }, [config, translations]);
101
+
102
+ const canContinue = state.image !== null;
103
+
104
+ return {
105
+ ...state,
106
+ setName,
107
+ setDescription,
108
+ handlePickImage,
109
+ canContinue,
110
+ };
111
+ };
112
+
113
+ export type UsePartnerStepReturn = ReturnType<typeof usePartnerStep>;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * PartnerStepScreen
3
+ * Generic partner/photo upload screen
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ useAppDesignTokens,
10
+ ScreenLayout,
11
+ AtomicText,
12
+ AtomicIcon,
13
+ NavigationHeader,
14
+ type DesignTokens,
15
+ } from "@umituz/react-native-design-system";
16
+ import { PhotoUploadCard } from "../../../../presentation/components";
17
+ import { FaceDetectionToggle } from "../../../../domains/face-detection";
18
+ import { PhotoTips } from "../components/PhotoTips";
19
+ import { PartnerInfoInput } from "../components/PartnerInfoInput";
20
+ import { usePartnerStep } from "../hooks/usePartnerStep";
21
+ import type { UploadedImage } from "../../domain/types";
22
+
23
+ export interface PartnerStepScreenTranslations {
24
+ readonly title: string;
25
+ readonly subtitle: string;
26
+ readonly continue: string;
27
+ readonly tapToUpload: string;
28
+ readonly selectPhoto: string;
29
+ readonly change: string;
30
+ readonly analyzing: string;
31
+ readonly faceDetectionLabel?: string;
32
+ readonly fileTooLarge: string;
33
+ readonly maxFileSize: string;
34
+ readonly error: string;
35
+ readonly uploadFailed: string;
36
+ }
37
+
38
+ export interface PartnerStepScreenConfig {
39
+ readonly showFaceDetection?: boolean;
40
+ readonly showNameInput?: boolean;
41
+ readonly showPhotoTips?: boolean;
42
+ readonly maxFileSizeMB?: number;
43
+ readonly maxNameLength?: number;
44
+ }
45
+
46
+ export interface PartnerStepScreenProps {
47
+ readonly translations: PartnerStepScreenTranslations;
48
+ readonly t: (key: string) => string;
49
+ readonly initialName?: string;
50
+ readonly config?: PartnerStepScreenConfig;
51
+ readonly faceDetectionEnabled?: boolean;
52
+ readonly onFaceDetectionToggle?: (enabled: boolean) => void;
53
+ readonly onBack: () => void;
54
+ readonly onContinue: (image: UploadedImage, name: string) => void;
55
+ }
56
+
57
+ const DEFAULT_CONFIG: PartnerStepScreenConfig = {
58
+ showFaceDetection: false,
59
+ showNameInput: false,
60
+ showPhotoTips: true,
61
+ maxFileSizeMB: 10,
62
+ maxNameLength: 30,
63
+ };
64
+
65
+ export const PartnerStepScreen: React.FC<PartnerStepScreenProps> = ({
66
+ translations,
67
+ t,
68
+ initialName = "",
69
+ config = DEFAULT_CONFIG,
70
+ faceDetectionEnabled = false,
71
+ onFaceDetectionToggle,
72
+ onBack,
73
+ onContinue,
74
+ }) => {
75
+ const tokens = useAppDesignTokens();
76
+
77
+ const { image, name, setName, handlePickImage, canContinue } = usePartnerStep({
78
+ initialName,
79
+ config: { maxFileSizeMB: config.maxFileSizeMB },
80
+ translations: {
81
+ fileTooLarge: translations.fileTooLarge,
82
+ maxFileSize: translations.maxFileSize,
83
+ error: translations.error,
84
+ uploadFailed: translations.uploadFailed,
85
+ },
86
+ });
87
+
88
+ const handleContinuePress = () => {
89
+ if (!canContinue || !image) return;
90
+ onContinue(image, name);
91
+ };
92
+
93
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
94
+ const showFaceDetection = config.showFaceDetection ?? false;
95
+ const showNameInput = config.showNameInput ?? false;
96
+ const showPhotoTips = config.showPhotoTips ?? true;
97
+
98
+ return (
99
+ <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
100
+ <NavigationHeader
101
+ title={translations.title}
102
+ onBackPress={onBack}
103
+ rightElement={
104
+ <TouchableOpacity
105
+ onPress={handleContinuePress}
106
+ activeOpacity={0.7}
107
+ disabled={!canContinue || !image}
108
+ style={[
109
+ styles.continueButton,
110
+ {
111
+ backgroundColor: canContinue && image ? tokens.colors.primary : tokens.colors.surfaceVariant,
112
+ opacity: canContinue && image ? 1 : 0.5,
113
+ },
114
+ ]}
115
+ >
116
+ <AtomicText
117
+ type="bodyMedium"
118
+ style={[
119
+ styles.continueText,
120
+ { color: canContinue && image ? tokens.colors.onPrimary : tokens.colors.textSecondary },
121
+ ]}
122
+ >
123
+ {translations.continue}
124
+ </AtomicText>
125
+ <AtomicIcon
126
+ name="arrow-forward"
127
+ size="sm"
128
+ color={canContinue && image ? "onPrimary" : "textSecondary"}
129
+ />
130
+ </TouchableOpacity>
131
+ }
132
+ />
133
+ <ScreenLayout
134
+ edges={["left", "right"]}
135
+ backgroundColor="transparent"
136
+ scrollable={true}
137
+ keyboardAvoiding={true}
138
+ contentContainerStyle={styles.scrollContent}
139
+ hideScrollIndicator={true}
140
+ >
141
+ <AtomicText style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
142
+ {translations.subtitle}
143
+ </AtomicText>
144
+
145
+ {showPhotoTips && <PhotoTips t={t} />}
146
+
147
+ {showFaceDetection && onFaceDetectionToggle && (
148
+ <FaceDetectionToggle
149
+ isEnabled={faceDetectionEnabled}
150
+ onToggle={onFaceDetectionToggle}
151
+ label={translations.faceDetectionLabel ?? ""}
152
+ hidden={true}
153
+ />
154
+ )}
155
+
156
+ <PhotoUploadCard
157
+ imageUri={image?.previewUrl || null}
158
+ onPress={handlePickImage}
159
+ isValidating={false}
160
+ isValid={null}
161
+ translations={{
162
+ tapToUpload: translations.tapToUpload,
163
+ selectPhoto: translations.selectPhoto,
164
+ change: translations.change,
165
+ analyzing: translations.analyzing,
166
+ }}
167
+ />
168
+
169
+ {showNameInput && (
170
+ <PartnerInfoInput
171
+ t={t}
172
+ name={name}
173
+ onNameChange={setName}
174
+ showName={true}
175
+ maxNameLength={config.maxNameLength}
176
+ />
177
+ )}
178
+ </ScreenLayout>
179
+ </View>
180
+ );
181
+ };
182
+
183
+ const createStyles = (tokens: DesignTokens) =>
184
+ StyleSheet.create({
185
+ container: {
186
+ flex: 1,
187
+ },
188
+ scrollContent: {
189
+ paddingBottom: 40,
190
+ },
191
+ subtitle: {
192
+ fontSize: 16,
193
+ textAlign: "center",
194
+ marginHorizontal: 24,
195
+ marginBottom: 24,
196
+ },
197
+ continueButton: {
198
+ flexDirection: "row",
199
+ alignItems: "center",
200
+ paddingHorizontal: tokens.spacing.md,
201
+ paddingVertical: tokens.spacing.xs,
202
+ borderRadius: tokens.borders.radius.full,
203
+ },
204
+ continueText: {
205
+ fontWeight: "800",
206
+ marginRight: 4,
207
+ },
208
+ });
@@ -0,0 +1,6 @@
1
+ export { PartnerStepScreen } from "./PartnerStepScreen";
2
+ export type {
3
+ PartnerStepScreenProps,
4
+ PartnerStepScreenTranslations,
5
+ PartnerStepScreenConfig,
6
+ } from "./PartnerStepScreen";
package/src/index.ts CHANGED
@@ -152,6 +152,7 @@ export * from "./features/hd-touch-up";
152
152
  export * from "./features/meme-generator";
153
153
  export * from "./features/couple-future";
154
154
  export * from "./features/love-message";
155
+ export * from "./features/partner-upload";
155
156
  export * from "./infrastructure/orchestration";
156
157
 
157
158
  // Result Preview Domain
@@ -3,9 +3,14 @@
3
3
  * Uses ONLY configured app services - no alternatives
4
4
  */
5
5
 
6
- import { readFileAsBase64 } from "@umituz/react-native-design-system";
6
+ import * as FileSystem from "expo-file-system";
7
7
  import { getAuthService, getCreditService, getPaywallService, isAppServicesConfigured } from "../config/app-services.config";
8
8
 
9
+ async function readFileAsBase64(uri: string): Promise<string> {
10
+ const base64 = await FileSystem.readAsStringAsync(uri, { encoding: "base64" });
11
+ return `data:image/jpeg;base64,${base64}`;
12
+ }
13
+
9
14
  declare const __DEV__: boolean;
10
15
 
11
16
  export type ImageSelector = () => Promise<string | null>;
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { useCallback, useMemo, useRef } from "react";
7
- import { useQueryClient } from "@umituz/react-native-design-system";
7
+ import { useQueryClient } from "@tanstack/react-query";
8
8
  import type {
9
9
  GenerationExecutionResult,
10
10
  GenerationCallbacks,