@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.233",
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,8 @@
1
+ /**
2
+ * Result Preview Domain Export
3
+ * Reusable result preview components for AI generation
4
+ */
5
+
6
+ export * from "./presentation/components";
7
+ export * from "./presentation/hooks";
8
+ export * from "./presentation/types";
@@ -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,7 @@
1
+ /**
2
+ * Result Preview Components Export
3
+ */
4
+
5
+ export { ResultPreviewScreen } from "./ResultPreviewScreen";
6
+ export { ResultImageCard } from "./ResultImageCard";
7
+ export { ResultActionBar } from "./ResultActionBar";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Result Preview Hooks Export
3
+ */
4
+
5
+ export { useResultActions } from "./useResultActions";
@@ -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,5 @@
1
+ /**
2
+ * Result Preview Types Export
3
+ */
4
+
5
+ export * from "./result-preview.types";
@@ -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,