@umituz/react-native-ai-generation-content 1.17.283 → 1.17.285

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.283",
3
+ "version": "1.17.285",
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",
@@ -15,6 +15,7 @@ import { useDeleteCreation } from "../hooks/useDeleteCreation";
15
15
  import { useGalleryFilters } from "../hooks/useGalleryFilters";
16
16
  import { GalleryHeader, CreationCard, GalleryEmptyStates } from "../components";
17
17
  import { ResultPreviewScreen } from "../../../result-preview/presentation/components/ResultPreviewScreen";
18
+ import { StarRatingPicker } from "../../../result-preview/presentation/components/StarRatingPicker";
18
19
  import { useResultActions } from "../../../result-preview/presentation/hooks/useResultActions";
19
20
  import { MEDIA_FILTER_OPTIONS, STATUS_FILTER_OPTIONS } from "../../domain/types/creation-filter";
20
21
  import { getPreviewUrl } from "../../domain/utils";
@@ -45,6 +46,7 @@ export function CreationsGalleryScreen({
45
46
  const { share } = useSharing();
46
47
  const alert = useAlert();
47
48
  const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
49
+ const [showRatingPicker, setShowRatingPicker] = useState(false);
48
50
 
49
51
  const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
50
52
  const deleteMutation = useDeleteCreation({ userId, repository });
@@ -101,12 +103,16 @@ export function CreationsGalleryScreen({
101
103
  setSelectedCreation(null);
102
104
  }, []);
103
105
 
104
- const handleRate = useCallback(() => {
106
+ const handleOpenRatingPicker = useCallback(() => {
107
+ setShowRatingPicker(true);
108
+ }, []);
109
+
110
+ const handleSubmitRating = useCallback((rating: number) => {
105
111
  if (!userId || !selectedCreation) return;
106
112
  void (async () => {
107
- const success = await repository.rate(userId, selectedCreation.id, 5);
113
+ const success = await repository.rate(userId, selectedCreation.id, rating);
108
114
  if (success) {
109
- setSelectedCreation({ ...selectedCreation, rating: 5, ratedAt: new Date() });
115
+ setSelectedCreation({ ...selectedCreation, rating, ratedAt: new Date() });
110
116
  alert.show(AlertType.SUCCESS, AlertMode.TOAST, t("result.rateSuccessTitle"), t("result.rateSuccessMessage"));
111
117
  void refetch();
112
118
  }
@@ -182,29 +188,39 @@ export function CreationsGalleryScreen({
182
188
  if (selectedCreation && selectedImageUrl) {
183
189
  const hasRating = selectedCreation.rating !== undefined && selectedCreation.rating !== null;
184
190
  return (
185
- <ResultPreviewScreen
186
- imageUrl={selectedImageUrl}
187
- isSaving={isSaving}
188
- isSharing={isSharing}
189
- onDownload={handleDownload}
190
- onShare={handleShare}
191
- onTryAgain={handleTryAgain}
192
- onNavigateBack={handleBack}
193
- onRate={handleRate}
194
- hideLabel
195
- iconOnly
196
- showTryAgain={false}
197
- showRating={!hasRating}
198
- translations={{
199
- title: t(config.translations.title),
200
- yourResult: "",
201
- saveButton: t("result.saveButton"),
202
- saving: t("result.saving"),
203
- shareButton: t("result.shareButton"),
204
- sharing: t("result.sharing"),
205
- tryAnother: "",
206
- }}
207
- />
191
+ <>
192
+ <ResultPreviewScreen
193
+ imageUrl={selectedImageUrl}
194
+ isSaving={isSaving}
195
+ isSharing={isSharing}
196
+ onDownload={handleDownload}
197
+ onShare={handleShare}
198
+ onTryAgain={handleTryAgain}
199
+ onNavigateBack={handleBack}
200
+ onRate={handleOpenRatingPicker}
201
+ hideLabel
202
+ iconOnly
203
+ showTryAgain={false}
204
+ showRating={!hasRating}
205
+ translations={{
206
+ title: t(config.translations.title),
207
+ yourResult: "",
208
+ saveButton: t("result.saveButton"),
209
+ saving: t("result.saving"),
210
+ shareButton: t("result.shareButton"),
211
+ sharing: t("result.sharing"),
212
+ tryAnother: "",
213
+ }}
214
+ />
215
+ <StarRatingPicker
216
+ visible={showRatingPicker}
217
+ onClose={() => setShowRatingPicker(false)}
218
+ onRate={handleSubmitRating}
219
+ title={t("result.rateTitle")}
220
+ submitLabel={t("common.submit")}
221
+ cancelLabel={t("common.cancel")}
222
+ />
223
+ </>
208
224
  );
209
225
  }
210
226
 
@@ -82,7 +82,7 @@ export const ResultActionBar: React.FC<ResultActionBarProps> = ({
82
82
  {isSaving ? (
83
83
  <ActivityIndicator color={tokens.colors.textInverse} size="small" />
84
84
  ) : (
85
- <AtomicIcon name="Download" customSize={24} color="onPrimary" />
85
+ <AtomicIcon name="download-outline" customSize={24} color="onPrimary" />
86
86
  )}
87
87
  </TouchableOpacity>
88
88
  <TouchableOpacity
@@ -94,7 +94,7 @@ export const ResultActionBar: React.FC<ResultActionBarProps> = ({
94
94
  {isSharing ? (
95
95
  <ActivityIndicator color={tokens.colors.textInverse} size="small" />
96
96
  ) : (
97
- <AtomicIcon name="Share2" customSize={24} color="onPrimary" />
97
+ <AtomicIcon name="share-social-outline" customSize={24} color="onPrimary" />
98
98
  )}
99
99
  </TouchableOpacity>
100
100
  {showRating && onRate && (
@@ -103,7 +103,7 @@ export const ResultActionBar: React.FC<ResultActionBarProps> = ({
103
103
  onPress={onRate}
104
104
  activeOpacity={0.7}
105
105
  >
106
- <AtomicIcon name="Star" customSize={24} color="onPrimary" />
106
+ <AtomicIcon name="star-outline" customSize={24} color="onPrimary" />
107
107
  </TouchableOpacity>
108
108
  )}
109
109
  </View>
@@ -0,0 +1,149 @@
1
+ /**
2
+ * StarRatingPicker Component
3
+ * Shows 5 stars for rating selection
4
+ */
5
+
6
+ import React, { useState, useMemo } from "react";
7
+ import { StyleSheet, View, TouchableOpacity, Modal } from "react-native";
8
+ import {
9
+ AtomicIcon,
10
+ AtomicText,
11
+ AtomicButton,
12
+ useAppDesignTokens,
13
+ } from "@umituz/react-native-design-system";
14
+
15
+ export interface StarRatingPickerProps {
16
+ visible: boolean;
17
+ onClose: () => void;
18
+ onRate: (rating: number) => void;
19
+ title?: string;
20
+ submitLabel?: string;
21
+ cancelLabel?: string;
22
+ }
23
+
24
+ export const StarRatingPicker: React.FC<StarRatingPickerProps> = ({
25
+ visible,
26
+ onClose,
27
+ onRate,
28
+ title = "Rate this creation",
29
+ submitLabel = "Submit",
30
+ cancelLabel = "Cancel",
31
+ }) => {
32
+ const tokens = useAppDesignTokens();
33
+ const [selectedRating, setSelectedRating] = useState(0);
34
+
35
+ const styles = useMemo(
36
+ () =>
37
+ StyleSheet.create({
38
+ overlay: {
39
+ flex: 1,
40
+ backgroundColor: "rgba(0, 0, 0, 0.6)",
41
+ justifyContent: "center",
42
+ alignItems: "center",
43
+ padding: tokens.spacing.lg,
44
+ },
45
+ container: {
46
+ backgroundColor: tokens.colors.surface,
47
+ borderRadius: 20,
48
+ padding: tokens.spacing.xl,
49
+ width: "100%",
50
+ maxWidth: 320,
51
+ alignItems: "center",
52
+ },
53
+ title: {
54
+ fontSize: 18,
55
+ fontWeight: "700",
56
+ color: tokens.colors.textPrimary,
57
+ marginBottom: tokens.spacing.lg,
58
+ textAlign: "center",
59
+ },
60
+ starsContainer: {
61
+ flexDirection: "row",
62
+ justifyContent: "center",
63
+ gap: 8,
64
+ marginBottom: tokens.spacing.xl,
65
+ },
66
+ starButton: {
67
+ padding: 4,
68
+ },
69
+ buttonsContainer: {
70
+ flexDirection: "row",
71
+ gap: tokens.spacing.md,
72
+ width: "100%",
73
+ },
74
+ button: {
75
+ flex: 1,
76
+ },
77
+ }),
78
+ [tokens],
79
+ );
80
+
81
+ const handleSubmit = () => {
82
+ if (selectedRating > 0) {
83
+ onRate(selectedRating);
84
+ setSelectedRating(0);
85
+ onClose();
86
+ }
87
+ };
88
+
89
+ const handleClose = () => {
90
+ setSelectedRating(0);
91
+ onClose();
92
+ };
93
+
94
+ return (
95
+ <Modal
96
+ visible={visible}
97
+ transparent
98
+ animationType="fade"
99
+ onRequestClose={handleClose}
100
+ >
101
+ <TouchableOpacity
102
+ style={styles.overlay}
103
+ activeOpacity={1}
104
+ onPress={handleClose}
105
+ >
106
+ <TouchableOpacity
107
+ style={styles.container}
108
+ activeOpacity={1}
109
+ onPress={() => {}}
110
+ >
111
+ <AtomicText style={styles.title}>{title}</AtomicText>
112
+ <View style={styles.starsContainer}>
113
+ {[1, 2, 3, 4, 5].map((star) => (
114
+ <TouchableOpacity
115
+ key={star}
116
+ style={styles.starButton}
117
+ onPress={() => setSelectedRating(star)}
118
+ activeOpacity={0.7}
119
+ >
120
+ <AtomicIcon
121
+ name={star <= selectedRating ? "star" : "star-outline"}
122
+ customSize={40}
123
+ customColor={star <= selectedRating ? "#FFD700" : tokens.colors.textTertiary}
124
+ />
125
+ </TouchableOpacity>
126
+ ))}
127
+ </View>
128
+ <View style={styles.buttonsContainer}>
129
+ <View style={styles.button}>
130
+ <AtomicButton
131
+ title={cancelLabel}
132
+ variant="secondary"
133
+ onPress={handleClose}
134
+ />
135
+ </View>
136
+ <View style={styles.button}>
137
+ <AtomicButton
138
+ title={submitLabel}
139
+ variant="primary"
140
+ onPress={handleSubmit}
141
+ disabled={selectedRating === 0}
142
+ />
143
+ </View>
144
+ </View>
145
+ </TouchableOpacity>
146
+ </TouchableOpacity>
147
+ </Modal>
148
+ );
149
+ };
@@ -7,6 +7,8 @@ export { ResultImageCard } from "./ResultImageCard";
7
7
  export { ResultActionBar } from "./ResultActionBar";
8
8
  export { RecentCreationsSection } from "./RecentCreationsSection";
9
9
  export { GenerationErrorScreen } from "./GenerationErrorScreen";
10
+ export { StarRatingPicker } from "./StarRatingPicker";
11
+ export type { StarRatingPickerProps } from "./StarRatingPicker";
10
12
  export type {
11
13
  GenerationErrorTranslations,
12
14
  GenerationErrorConfig,
@@ -5,8 +5,8 @@
5
5
 
6
6
  import { useState, useCallback } from "react";
7
7
  import { Alert } from "react-native";
8
- import * as ImagePicker from "expo-image-picker";
9
8
  import * as FileSystem from "expo-file-system";
9
+ import { useMedia, MediaLibraryPermission } from "@umituz/react-native-design-system";
10
10
  import type { UploadedImage } from "../../domain/types";
11
11
 
12
12
  export interface UsePartnerStepConfig {
@@ -20,6 +20,8 @@ export interface UsePartnerStepTranslations {
20
20
  readonly maxFileSize: string;
21
21
  readonly error: string;
22
22
  readonly uploadFailed: string;
23
+ readonly permissionDenied?: string;
24
+ readonly permissionRequired?: string;
23
25
  }
24
26
 
25
27
  export interface UsePartnerStepOptions {
@@ -42,6 +44,7 @@ const DEFAULT_CONFIG: UsePartnerStepConfig = {
42
44
 
43
45
  export const usePartnerStep = (options: UsePartnerStepOptions) => {
44
46
  const { initialName = "", config = DEFAULT_CONFIG, translations } = options;
47
+ const { pickImage, requestMediaLibraryPermission, getMediaLibraryPermissionStatus, isLoading: isPickerLoading } = useMedia();
45
48
 
46
49
  const [state, setState] = useState<PartnerStepState>({
47
50
  image: null,
@@ -59,9 +62,21 @@ export const usePartnerStep = (options: UsePartnerStepOptions) => {
59
62
 
60
63
  const handlePickImage = useCallback(async () => {
61
64
  try {
65
+ // Check permission first
66
+ let permission = await getMediaLibraryPermissionStatus();
67
+ if (permission !== MediaLibraryPermission.GRANTED) {
68
+ permission = await requestMediaLibraryPermission();
69
+ if (permission !== MediaLibraryPermission.GRANTED) {
70
+ Alert.alert(
71
+ translations.error,
72
+ translations.permissionDenied ?? "Photo library access is required to upload images.",
73
+ );
74
+ return;
75
+ }
76
+ }
77
+
62
78
  const maxFileSizeMB = config.maxFileSizeMB ?? 10;
63
- const result = await ImagePicker.launchImageLibraryAsync({
64
- mediaTypes: ImagePicker.MediaTypeOptions.Images,
79
+ const result = await pickImage({
65
80
  allowsEditing: config.allowsEditing ?? true,
66
81
  quality: config.imageQuality ?? 0.7,
67
82
  });
@@ -97,7 +112,7 @@ export const usePartnerStep = (options: UsePartnerStepOptions) => {
97
112
  } catch {
98
113
  Alert.alert(translations.error, translations.uploadFailed);
99
114
  }
100
- }, [config, translations]);
115
+ }, [config, translations, pickImage, requestMediaLibraryPermission, getMediaLibraryPermissionStatus]);
101
116
 
102
117
  const canContinue = state.image !== null;
103
118
 
@@ -107,6 +122,7 @@ export const usePartnerStep = (options: UsePartnerStepOptions) => {
107
122
  setDescription,
108
123
  handlePickImage,
109
124
  canContinue,
125
+ isLoading: isPickerLoading,
110
126
  };
111
127
  };
112
128