@umituz/react-native-ai-generation-content 1.17.233 → 1.17.234
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 -2
- package/src/domains/result-preview/index.ts +8 -0
- package/src/domains/result-preview/presentation/components/ResultActionBar.tsx +74 -0
- package/src/domains/result-preview/presentation/components/ResultImageCard.tsx +64 -0
- package/src/domains/result-preview/presentation/components/ResultPreviewScreen.tsx +101 -0
- package/src/domains/result-preview/presentation/components/index.ts +7 -0
- package/src/domains/result-preview/presentation/hooks/index.ts +5 -0
- package/src/domains/result-preview/presentation/hooks/useResultActions.ts +148 -0
- package/src/domains/result-preview/presentation/types/index.ts +5 -0
- package/src/domains/result-preview/presentation/types/result-preview.types.ts +146 -0
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
4
|
-
"description": "Provider-agnostic AI generation orchestration for React Native",
|
|
3
|
+
"version": "1.17.234",
|
|
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",
|
|
7
7
|
"exports": {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResultActionBar Component
|
|
3
|
+
* Action buttons for save, share, and try again
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { StyleSheet, View } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicButton,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
12
|
+
import type { ResultActionBarProps } from "../types/result-preview.types";
|
|
13
|
+
|
|
14
|
+
export const ResultActionBar: React.FC<ResultActionBarProps> = ({
|
|
15
|
+
isSaving,
|
|
16
|
+
isSharing,
|
|
17
|
+
onDownload,
|
|
18
|
+
onShare,
|
|
19
|
+
onTryAgain,
|
|
20
|
+
saveButtonText,
|
|
21
|
+
saveButtonLoadingText,
|
|
22
|
+
shareButtonText,
|
|
23
|
+
shareButtonLoadingText,
|
|
24
|
+
tryAgainButtonText,
|
|
25
|
+
}) => {
|
|
26
|
+
const tokens = useAppDesignTokens();
|
|
27
|
+
|
|
28
|
+
const styles = useMemo(
|
|
29
|
+
() =>
|
|
30
|
+
StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
flexDirection: "row",
|
|
33
|
+
gap: tokens.spacing.md,
|
|
34
|
+
marginBottom: tokens.spacing.lg,
|
|
35
|
+
},
|
|
36
|
+
button: {
|
|
37
|
+
flex: 1,
|
|
38
|
+
},
|
|
39
|
+
tryAgainButton: {
|
|
40
|
+
marginTop: tokens.spacing.md,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
[tokens],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<View style={styles.container}>
|
|
48
|
+
<AtomicButton
|
|
49
|
+
title={isSaving ? saveButtonLoadingText : saveButtonText}
|
|
50
|
+
onPress={onDownload}
|
|
51
|
+
disabled={isSaving}
|
|
52
|
+
variant="secondary"
|
|
53
|
+
icon="download"
|
|
54
|
+
style={styles.button}
|
|
55
|
+
/>
|
|
56
|
+
<AtomicButton
|
|
57
|
+
title={isSharing ? shareButtonLoadingText : shareButtonText}
|
|
58
|
+
onPress={onShare}
|
|
59
|
+
disabled={isSharing}
|
|
60
|
+
variant="primary"
|
|
61
|
+
icon="share-social"
|
|
62
|
+
style={styles.button}
|
|
63
|
+
/>
|
|
64
|
+
<AtomicButton
|
|
65
|
+
title={tryAgainButtonText}
|
|
66
|
+
onPress={onTryAgain}
|
|
67
|
+
variant="text"
|
|
68
|
+
icon="refresh"
|
|
69
|
+
fullWidth
|
|
70
|
+
style={styles.tryAgainButton}
|
|
71
|
+
/>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResultImageCard Component
|
|
3
|
+
* Displays AI generation result image with proper styling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { StyleSheet, View, ViewStyle } from "react-native";
|
|
8
|
+
import { AtomicImage } from "@umituz/react-native-image";
|
|
9
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
10
|
+
import type { ResultImageCardProps } from "../types/result-preview.types";
|
|
11
|
+
|
|
12
|
+
export const ResultImageCard: React.FC<ResultImageCardProps> = ({
|
|
13
|
+
imageUrl,
|
|
14
|
+
style,
|
|
15
|
+
rounded = true,
|
|
16
|
+
}) => {
|
|
17
|
+
const tokens = useAppDesignTokens();
|
|
18
|
+
|
|
19
|
+
const styles = useMemo(
|
|
20
|
+
() =>
|
|
21
|
+
StyleSheet.create({
|
|
22
|
+
container: {
|
|
23
|
+
width: "100%",
|
|
24
|
+
aspectRatio: 1,
|
|
25
|
+
borderRadius: rounded ? tokens.borders.radius.md : 0,
|
|
26
|
+
overflow: "hidden",
|
|
27
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
28
|
+
},
|
|
29
|
+
image: {
|
|
30
|
+
width: "100%",
|
|
31
|
+
height: "100%",
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
[tokens, rounded],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const displayImageUrl = useMemo(() => {
|
|
38
|
+
if (!imageUrl) return null;
|
|
39
|
+
|
|
40
|
+
// If not a URL and not a data URL, assume it's base64
|
|
41
|
+
if (
|
|
42
|
+
!imageUrl.startsWith("http") &&
|
|
43
|
+
!imageUrl.startsWith("data:image")
|
|
44
|
+
) {
|
|
45
|
+
return `data:image/jpeg;base64,${imageUrl}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return imageUrl;
|
|
49
|
+
}, [imageUrl]);
|
|
50
|
+
|
|
51
|
+
if (!displayImageUrl) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View style={[styles.container, style]}>
|
|
57
|
+
<AtomicImage
|
|
58
|
+
source={{ uri: displayImageUrl }}
|
|
59
|
+
style={styles.image}
|
|
60
|
+
rounded={rounded}
|
|
61
|
+
/>
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResultPreviewScreen Component
|
|
3
|
+
* Displays AI generation result with actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { StyleSheet, View } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
ScreenLayout,
|
|
12
|
+
NavigationHeader,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { ResultImageCard } from "./ResultImageCard";
|
|
15
|
+
import { ResultActionBar } from "./ResultActionBar";
|
|
16
|
+
import type { ResultPreviewScreenProps } from "../types/result-preview.types";
|
|
17
|
+
|
|
18
|
+
export const ResultPreviewScreen: React.FC<ResultPreviewScreenProps> = ({
|
|
19
|
+
imageUrl,
|
|
20
|
+
isSaving,
|
|
21
|
+
isSharing,
|
|
22
|
+
onDownload,
|
|
23
|
+
onShare,
|
|
24
|
+
onTryAgain,
|
|
25
|
+
onNavigateBack,
|
|
26
|
+
translations,
|
|
27
|
+
style,
|
|
28
|
+
}) => {
|
|
29
|
+
const tokens = useAppDesignTokens();
|
|
30
|
+
|
|
31
|
+
const styles = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
StyleSheet.create({
|
|
34
|
+
container: {
|
|
35
|
+
flex: 1,
|
|
36
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
37
|
+
},
|
|
38
|
+
resultContainer: {
|
|
39
|
+
marginTop: tokens.spacing.lg,
|
|
40
|
+
},
|
|
41
|
+
title: {
|
|
42
|
+
fontSize: 18,
|
|
43
|
+
fontWeight: "700",
|
|
44
|
+
color: tokens.colors.textPrimary,
|
|
45
|
+
marginBottom: tokens.spacing.md,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
[tokens],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const displayImageUrl = useMemo(() => {
|
|
52
|
+
if (!imageUrl) return null;
|
|
53
|
+
|
|
54
|
+
// If not a URL and not a data URL, assume it's base64
|
|
55
|
+
if (
|
|
56
|
+
!imageUrl.startsWith("http") &&
|
|
57
|
+
!imageUrl.startsWith("data:image")
|
|
58
|
+
) {
|
|
59
|
+
return `data:image/jpeg;base64,${imageUrl}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return imageUrl;
|
|
63
|
+
}, [imageUrl]);
|
|
64
|
+
|
|
65
|
+
if (!displayImageUrl) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ScreenLayout
|
|
71
|
+
scrollable
|
|
72
|
+
edges={["left", "right"]}
|
|
73
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
74
|
+
>
|
|
75
|
+
<NavigationHeader
|
|
76
|
+
title={translations.title}
|
|
77
|
+
onBackPress={onNavigateBack}
|
|
78
|
+
/>
|
|
79
|
+
<View style={[styles.container, style]}>
|
|
80
|
+
<View style={styles.resultContainer}>
|
|
81
|
+
<AtomicText style={styles.title}>
|
|
82
|
+
{translations.yourResult}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
<ResultImageCard imageUrl={displayImageUrl} />
|
|
85
|
+
<ResultActionBar
|
|
86
|
+
isSaving={isSaving}
|
|
87
|
+
isSharing={isSharing}
|
|
88
|
+
onDownload={onDownload}
|
|
89
|
+
onShare={onShare}
|
|
90
|
+
onTryAgain={onTryAgain}
|
|
91
|
+
saveButtonText={translations.saveButton}
|
|
92
|
+
saveButtonLoadingText={translations.saving}
|
|
93
|
+
shareButtonText={translations.shareButton}
|
|
94
|
+
shareButtonLoadingText={translations.sharing}
|
|
95
|
+
tryAgainButtonText={translations.tryAnother}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
</ScreenLayout>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useResultActions Hook
|
|
3
|
+
* Handles save and share actions for AI generation results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import * as MediaLibrary from "expo-media-library";
|
|
8
|
+
import * as Sharing from "expo-sharing";
|
|
9
|
+
import { File, Paths } from "expo-file-system/next";
|
|
10
|
+
import type {
|
|
11
|
+
UseResultActionsOptions,
|
|
12
|
+
UseResultActionsReturn,
|
|
13
|
+
} from "../types/result-preview.types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a string is a base64 data URL
|
|
17
|
+
*/
|
|
18
|
+
const isBase64DataUrl = (str: string): boolean => {
|
|
19
|
+
return str.startsWith("data:image/");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Save base64 image to file system
|
|
24
|
+
*/
|
|
25
|
+
const saveBase64ToFile = async (
|
|
26
|
+
base64Data: string,
|
|
27
|
+
): Promise<string> => {
|
|
28
|
+
const timestamp = Date.now();
|
|
29
|
+
const filename = `ai_generation_${timestamp}.jpg`;
|
|
30
|
+
const file = new File(Paths.cache, filename);
|
|
31
|
+
|
|
32
|
+
// Extract pure base64 data (remove data URL prefix if present)
|
|
33
|
+
const pureBase64 = base64Data.replace(/^data:image\/\w+;base64,/, "");
|
|
34
|
+
|
|
35
|
+
// Convert base64 to Uint8Array
|
|
36
|
+
const binaryString = atob(pureBase64);
|
|
37
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
38
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
39
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Write file
|
|
43
|
+
file.write(bytes);
|
|
44
|
+
|
|
45
|
+
return file.uri;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hook for managing result actions (save/share)
|
|
50
|
+
*/
|
|
51
|
+
export const useResultActions = (
|
|
52
|
+
options: UseResultActionsOptions = {},
|
|
53
|
+
): UseResultActionsReturn => {
|
|
54
|
+
const {
|
|
55
|
+
imageUrl,
|
|
56
|
+
onSaveSuccess,
|
|
57
|
+
onSaveError,
|
|
58
|
+
onShareStart,
|
|
59
|
+
onShareEnd,
|
|
60
|
+
} = options;
|
|
61
|
+
|
|
62
|
+
const [isSharing, setIsSharing] = useState(false);
|
|
63
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Save image to device gallery
|
|
67
|
+
*/
|
|
68
|
+
const handleDownload = useCallback(async () => {
|
|
69
|
+
if (!imageUrl) {
|
|
70
|
+
onSaveError?.(new Error("No image URL provided"));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
setIsSaving(true);
|
|
76
|
+
|
|
77
|
+
// Request permissions
|
|
78
|
+
const { status } = await MediaLibrary.requestPermissionsAsync();
|
|
79
|
+
if (status !== "granted") {
|
|
80
|
+
throw new Error("Media library permission not granted");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let fileUri = imageUrl;
|
|
84
|
+
|
|
85
|
+
// If it's a base64 string, save to file first
|
|
86
|
+
if (isBase64DataUrl(fileUri)) {
|
|
87
|
+
fileUri = await saveBase64ToFile(fileUri);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Save to media library
|
|
91
|
+
await MediaLibrary.saveToLibraryAsync(fileUri);
|
|
92
|
+
|
|
93
|
+
onSaveSuccess?.();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const errorObj =
|
|
96
|
+
error instanceof Error ? error : new Error(String(error));
|
|
97
|
+
onSaveError?.(errorObj);
|
|
98
|
+
} finally {
|
|
99
|
+
setIsSaving(false);
|
|
100
|
+
}
|
|
101
|
+
}, [imageUrl, onSaveSuccess, onSaveError]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Share image
|
|
105
|
+
*/
|
|
106
|
+
const handleShare = useCallback(async () => {
|
|
107
|
+
if (!imageUrl) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
setIsSharing(true);
|
|
113
|
+
onShareStart?.();
|
|
114
|
+
|
|
115
|
+
let shareUrl = imageUrl;
|
|
116
|
+
|
|
117
|
+
// If it's a base64 string, save to file first for sharing
|
|
118
|
+
if (isBase64DataUrl(shareUrl)) {
|
|
119
|
+
shareUrl = await saveBase64ToFile(shareUrl);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Use expo-sharing for cross-platform file sharing
|
|
123
|
+
const isAvailable = await Sharing.isAvailableAsync();
|
|
124
|
+
if (isAvailable) {
|
|
125
|
+
await Sharing.shareAsync(shareUrl, {
|
|
126
|
+
mimeType: "image/jpeg",
|
|
127
|
+
dialogTitle: "Share Image",
|
|
128
|
+
});
|
|
129
|
+
onShareEnd?.(false);
|
|
130
|
+
} else {
|
|
131
|
+
onShareEnd?.(true);
|
|
132
|
+
}
|
|
133
|
+
} catch (error: unknown) {
|
|
134
|
+
// User cancelled or error - silently handle
|
|
135
|
+
if (__DEV__) console.log("Share cancelled or failed:", error);
|
|
136
|
+
onShareEnd?.(true);
|
|
137
|
+
} finally {
|
|
138
|
+
setIsSharing(false);
|
|
139
|
+
}
|
|
140
|
+
}, [imageUrl, onShareStart, onShareEnd]);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
isSaving,
|
|
144
|
+
isSharing,
|
|
145
|
+
handleDownload,
|
|
146
|
+
handleShare,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Preview Domain Types
|
|
3
|
+
* Reusable result preview components for AI generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StyleProp, ViewStyle } from "react-native";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result data structure
|
|
10
|
+
*/
|
|
11
|
+
export interface ResultData {
|
|
12
|
+
imageUrl: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Result actions callbacks
|
|
18
|
+
*/
|
|
19
|
+
export interface ResultActionsCallbacks {
|
|
20
|
+
/** Download/save result */
|
|
21
|
+
onDownload: () => void | Promise<void>;
|
|
22
|
+
/** Share result */
|
|
23
|
+
onShare: () => void | Promise<void>;
|
|
24
|
+
/** Try again with same inputs */
|
|
25
|
+
onTryAgain: () => void | Promise<void>;
|
|
26
|
+
/** Navigate back */
|
|
27
|
+
onNavigateBack: () => void | Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result display state
|
|
32
|
+
*/
|
|
33
|
+
export interface ResultDisplayState {
|
|
34
|
+
/** Currently saving */
|
|
35
|
+
isSaving: boolean;
|
|
36
|
+
/** Currently sharing */
|
|
37
|
+
isSharing: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Result image card props
|
|
42
|
+
*/
|
|
43
|
+
export interface ResultImageCardProps {
|
|
44
|
+
/** Image URL to display */
|
|
45
|
+
imageUrl: string;
|
|
46
|
+
/** Optional custom style */
|
|
47
|
+
style?: StyleProp<ViewStyle>;
|
|
48
|
+
/** Image rounded corners */
|
|
49
|
+
rounded?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Result action bar props
|
|
54
|
+
*/
|
|
55
|
+
export interface ResultActionBarProps {
|
|
56
|
+
/** Currently saving */
|
|
57
|
+
isSaving: boolean;
|
|
58
|
+
/** Currently sharing */
|
|
59
|
+
isSharing: boolean;
|
|
60
|
+
/** Download callback */
|
|
61
|
+
onDownload: () => void;
|
|
62
|
+
/** Share callback */
|
|
63
|
+
onShare: () => void;
|
|
64
|
+
/** Try again callback */
|
|
65
|
+
onTryAgain: () => void;
|
|
66
|
+
/** Save button text */
|
|
67
|
+
saveButtonText: string;
|
|
68
|
+
/** Save button text when loading */
|
|
69
|
+
saveButtonLoadingText: string;
|
|
70
|
+
/** Share button text */
|
|
71
|
+
shareButtonText: string;
|
|
72
|
+
/** Share button text when loading */
|
|
73
|
+
shareButtonLoadingText: string;
|
|
74
|
+
/** Try again button text */
|
|
75
|
+
tryAgainButtonText: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Result preview screen props
|
|
80
|
+
*/
|
|
81
|
+
export interface ResultPreviewScreenProps {
|
|
82
|
+
/** Result data to display */
|
|
83
|
+
imageUrl: string;
|
|
84
|
+
/** Result display state */
|
|
85
|
+
isSaving: boolean;
|
|
86
|
+
isSharing: boolean;
|
|
87
|
+
/** Action callbacks */
|
|
88
|
+
onDownload: () => void;
|
|
89
|
+
onShare: () => void;
|
|
90
|
+
onTryAgain: () => void;
|
|
91
|
+
onNavigateBack: () => void;
|
|
92
|
+
/** Translations */
|
|
93
|
+
translations: ResultPreviewTranslations;
|
|
94
|
+
/** Optional custom style */
|
|
95
|
+
style?: StyleProp<ViewStyle>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Result preview translations
|
|
100
|
+
*/
|
|
101
|
+
export interface ResultPreviewTranslations {
|
|
102
|
+
/** Screen title */
|
|
103
|
+
title: string;
|
|
104
|
+
/** Result label */
|
|
105
|
+
yourResult: string;
|
|
106
|
+
/** Save button */
|
|
107
|
+
saveButton: string;
|
|
108
|
+
/** Saving button */
|
|
109
|
+
saving: string;
|
|
110
|
+
/** Share button */
|
|
111
|
+
shareButton: string;
|
|
112
|
+
/** Sharing button */
|
|
113
|
+
sharing: string;
|
|
114
|
+
/** Try again button */
|
|
115
|
+
tryAnother: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Use result actions options
|
|
120
|
+
*/
|
|
121
|
+
export interface UseResultActionsOptions {
|
|
122
|
+
/** Image URL to save/share */
|
|
123
|
+
imageUrl?: string;
|
|
124
|
+
/** Callback on save success */
|
|
125
|
+
onSaveSuccess?: () => void;
|
|
126
|
+
/** Callback on save error */
|
|
127
|
+
onSaveError?: (error: Error) => void;
|
|
128
|
+
/** Callback on share start */
|
|
129
|
+
onShareStart?: () => void;
|
|
130
|
+
/** Callback on share end */
|
|
131
|
+
onShareEnd?: (cancelled?: boolean) => void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Use result actions return
|
|
136
|
+
*/
|
|
137
|
+
export interface UseResultActionsReturn {
|
|
138
|
+
/** Currently saving */
|
|
139
|
+
isSaving: boolean;
|
|
140
|
+
/** Currently sharing */
|
|
141
|
+
isSharing: boolean;
|
|
142
|
+
/** Save/download handler */
|
|
143
|
+
handleDownload: () => Promise<void>;
|
|
144
|
+
/** Share handler */
|
|
145
|
+
handleShare: () => Promise<void>;
|
|
146
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -153,6 +153,9 @@ export * from "./features/meme-generator";
|
|
|
153
153
|
export * from "./features/couple-future";
|
|
154
154
|
export * from "./infrastructure/orchestration";
|
|
155
155
|
|
|
156
|
+
// Result Preview Domain
|
|
157
|
+
export * from "./domains/result-preview";
|
|
158
|
+
|
|
156
159
|
// Unified AI Feature Screen
|
|
157
160
|
export {
|
|
158
161
|
AIFeatureScreen,
|