@umituz/react-native-ai-generation-content 1.12.3 → 1.12.5

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.
Files changed (82) hide show
  1. package/package.json +30 -6
  2. package/src/domains/content-moderation/domain/entities/moderation.types.ts +84 -0
  3. package/src/domains/content-moderation/domain/interfaces/content-filter.interface.ts +24 -0
  4. package/src/domains/content-moderation/index.ts +67 -0
  5. package/src/domains/content-moderation/infrastructure/rules/default-rules.data.ts +144 -0
  6. package/src/domains/content-moderation/infrastructure/rules/rules-registry.ts +75 -0
  7. package/src/domains/content-moderation/infrastructure/services/content-moderation.service.ts +150 -0
  8. package/src/domains/content-moderation/infrastructure/services/index.ts +8 -0
  9. package/src/domains/content-moderation/infrastructure/services/moderators/base.moderator.ts +62 -0
  10. package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +64 -0
  11. package/src/domains/content-moderation/infrastructure/services/moderators/index.ts +10 -0
  12. package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +144 -0
  13. package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +64 -0
  14. package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +74 -0
  15. package/src/domains/content-moderation/infrastructure/services/pattern-matcher.service.ts +51 -0
  16. package/src/domains/content-moderation/presentation/exceptions/content-policy-violation.exception.ts +48 -0
  17. package/src/domains/creations/application/services/CreationsService.ts +71 -0
  18. package/src/domains/creations/domain/entities/Creation.ts +51 -0
  19. package/src/domains/creations/domain/entities/index.ts +6 -0
  20. package/src/domains/creations/domain/repositories/ICreationsRepository.ts +23 -0
  21. package/src/domains/creations/domain/repositories/index.ts +5 -0
  22. package/src/domains/creations/domain/services/ICreationsStorageService.ts +13 -0
  23. package/src/domains/creations/domain/value-objects/CreationsConfig.ts +76 -0
  24. package/src/domains/creations/domain/value-objects/index.ts +12 -0
  25. package/src/domains/creations/index.ts +84 -0
  26. package/src/domains/creations/infrastructure/adapters/createRepository.ts +54 -0
  27. package/src/domains/creations/infrastructure/adapters/index.ts +5 -0
  28. package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +233 -0
  29. package/src/domains/creations/infrastructure/repositories/index.ts +8 -0
  30. package/src/domains/creations/infrastructure/services/CreationsStorageService.ts +48 -0
  31. package/src/domains/creations/presentation/components/CreationCard.tsx +136 -0
  32. package/src/domains/creations/presentation/components/CreationDetail/DetailActions.tsx +76 -0
  33. package/src/domains/creations/presentation/components/CreationDetail/DetailHeader.tsx +81 -0
  34. package/src/domains/creations/presentation/components/CreationDetail/DetailImage.tsx +41 -0
  35. package/src/domains/creations/presentation/components/CreationDetail/DetailStory.tsx +67 -0
  36. package/src/domains/creations/presentation/components/CreationDetail/index.ts +4 -0
  37. package/src/domains/creations/presentation/components/CreationImageViewer.tsx +43 -0
  38. package/src/domains/creations/presentation/components/CreationThumbnail.tsx +63 -0
  39. package/src/domains/creations/presentation/components/CreationsGrid.tsx +75 -0
  40. package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +176 -0
  41. package/src/domains/creations/presentation/components/EmptyState.tsx +75 -0
  42. package/src/domains/creations/presentation/components/FilterBottomSheet.tsx +158 -0
  43. package/src/domains/creations/presentation/components/FilterChips.tsx +105 -0
  44. package/src/domains/creations/presentation/components/GalleryHeader.tsx +106 -0
  45. package/src/domains/creations/presentation/components/index.ts +19 -0
  46. package/src/domains/creations/presentation/hooks/index.ts +7 -0
  47. package/src/domains/creations/presentation/hooks/useCreations.ts +33 -0
  48. package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +70 -0
  49. package/src/domains/creations/presentation/hooks/useDeleteCreation.ts +51 -0
  50. package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +71 -0
  51. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +217 -0
  52. package/src/domains/creations/presentation/screens/index.ts +5 -0
  53. package/src/domains/creations/presentation/utils/filterUtils.ts +52 -0
  54. package/src/domains/creations/types.d.ts +107 -0
  55. package/src/domains/face-detection/domain/constants/faceDetectionConstants.ts +16 -0
  56. package/src/domains/face-detection/domain/entities/FaceDetection.ts +19 -0
  57. package/src/domains/face-detection/index.ts +26 -0
  58. package/src/domains/face-detection/infrastructure/analyzers/faceAnalyzer.ts +36 -0
  59. package/src/domains/face-detection/infrastructure/validators/faceValidator.ts +52 -0
  60. package/src/domains/face-detection/presentation/components/FaceValidationStatus.tsx +111 -0
  61. package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +58 -0
  62. package/src/domains/feature-background/domain/entities/background.types.ts +77 -0
  63. package/src/domains/feature-background/domain/entities/component.types.ts +96 -0
  64. package/src/domains/feature-background/domain/entities/config.types.ts +41 -0
  65. package/src/domains/feature-background/domain/entities/index.ts +31 -0
  66. package/src/domains/feature-background/index.ts +72 -0
  67. package/src/domains/feature-background/infrastructure/constants/index.ts +5 -0
  68. package/src/domains/feature-background/infrastructure/constants/prompts.constants.ts +15 -0
  69. package/src/domains/feature-background/presentation/components/BackgroundFeature.tsx +145 -0
  70. package/src/domains/feature-background/presentation/components/ComparisonSlider.tsx +199 -0
  71. package/src/domains/feature-background/presentation/components/ErrorDisplay.tsx +58 -0
  72. package/src/domains/feature-background/presentation/components/FeatureHeader.tsx +80 -0
  73. package/src/domains/feature-background/presentation/components/GenerateButton.tsx +86 -0
  74. package/src/domains/feature-background/presentation/components/ImagePicker.tsx +136 -0
  75. package/src/domains/feature-background/presentation/components/ModeSelector.tsx +78 -0
  76. package/src/domains/feature-background/presentation/components/ProcessingModal.tsx +113 -0
  77. package/src/domains/feature-background/presentation/components/PromptInput.tsx +142 -0
  78. package/src/domains/feature-background/presentation/components/ResultDisplay.tsx +123 -0
  79. package/src/domains/feature-background/presentation/components/index.ts +16 -0
  80. package/src/domains/feature-background/presentation/hooks/index.ts +7 -0
  81. package/src/domains/feature-background/presentation/hooks/useBackgroundFeature.ts +118 -0
  82. package/src/index.ts +24 -0
@@ -0,0 +1,71 @@
1
+
2
+ import React from 'react';
3
+ import { View, StyleSheet, ScrollView } from 'react-native';
4
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
+ import type { Creation } from '../../domain/entities/Creation';
6
+ import { DetailHeader } from '../components/CreationDetail/DetailHeader';
7
+ import { DetailImage } from '../components/CreationDetail/DetailImage';
8
+ import { DetailStory } from '../components/CreationDetail/DetailStory';
9
+ import { DetailActions } from '../components/CreationDetail/DetailActions';
10
+
11
+ interface CreationDetailScreenProps {
12
+ readonly creation: Creation;
13
+ readonly onClose: () => void;
14
+ readonly onShare: (creation: Creation) => void;
15
+ readonly onDelete: (creation: Creation) => void;
16
+ readonly t: (key: string) => string;
17
+ }
18
+
19
+ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
20
+ creation,
21
+ onClose,
22
+ onShare,
23
+ onDelete,
24
+ t
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+
28
+ // Extract data
29
+ const metadata = (creation as any).metadata || {};
30
+ const title = metadata.names || creation.type;
31
+ const story = metadata.story || metadata.description || "";
32
+ const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
33
+
34
+ const styles = useStyles(tokens);
35
+
36
+ return (
37
+ <View style={styles.container}>
38
+ <DetailHeader
39
+ title={title}
40
+ date={date}
41
+ onClose={onClose}
42
+ />
43
+
44
+ <ScrollView
45
+ contentContainerStyle={styles.scrollContent}
46
+ showsVerticalScrollIndicator={false}
47
+ >
48
+ <DetailImage uri={creation.uri} />
49
+
50
+ <DetailStory story={story} />
51
+
52
+ <DetailActions
53
+ onShare={() => onShare(creation)}
54
+ onDelete={() => onDelete(creation)}
55
+ shareLabel={t("result.shareButton") || "Share"}
56
+ deleteLabel={t("common.delete") || "Delete"}
57
+ />
58
+ </ScrollView>
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const useStyles = (tokens: any) => StyleSheet.create({
64
+ container: {
65
+ flex: 1,
66
+ backgroundColor: tokens.colors.backgroundPrimary,
67
+ },
68
+ scrollContent: {
69
+ paddingBottom: tokens.spacing.xxl,
70
+ },
71
+ });
@@ -0,0 +1,217 @@
1
+ import React, { useMemo, useCallback, useState } from "react";
2
+ import { View, StyleSheet, ActivityIndicator } from "react-native";
3
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
4
+ import { useSharing } from "@umituz/react-native-sharing";
5
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
+ import { useFocusEffect } from "@react-navigation/native";
7
+ import { useCreations } from "../hooks/useCreations";
8
+ import { useDeleteCreation } from "../hooks/useDeleteCreation";
9
+ import { useCreationsFilter } from "../hooks/useCreationsFilter";
10
+ import { useAlert } from "@umituz/react-native-alert";
11
+ import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
12
+ import { GalleryHeader, EmptyState, CreationsGrid, FilterBottomSheet, CreationImageViewer, type FilterCategory } from "../components";
13
+ import { getTranslatedTypes, getFilterCategoriesFromConfig } from "../utils/filterUtils";
14
+ import type { Creation } from "../../domain/entities/Creation";
15
+ import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
16
+ import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
17
+ import { CreationDetailScreen } from "./CreationDetailScreen";
18
+
19
+ interface CreationsGalleryScreenProps {
20
+ readonly userId: string | null;
21
+ readonly repository: ICreationsRepository;
22
+ readonly config: CreationsConfig;
23
+ readonly t: (key: string) => string;
24
+ readonly enableEditing?: boolean;
25
+ readonly onImageEdit?: (uri: string, creationId: string) => void | Promise<void>;
26
+ readonly onEmptyAction?: () => void;
27
+ readonly emptyActionLabel?: string;
28
+ }
29
+
30
+ export function CreationsGalleryScreen({
31
+ userId,
32
+ repository,
33
+ config,
34
+ t,
35
+ enableEditing = false,
36
+ onImageEdit,
37
+ onEmptyAction,
38
+ emptyActionLabel,
39
+ }: CreationsGalleryScreenProps) {
40
+ const tokens = useAppDesignTokens();
41
+ const insets = useSafeAreaInsets();
42
+ const { share } = useSharing();
43
+ const alert = useAlert();
44
+
45
+ const [viewerVisible, setViewerVisible] = useState(false);
46
+ const [viewerIndex, setViewerIndex] = useState(0);
47
+ const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
48
+ const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
49
+
50
+ const { data: creationsData, isLoading, refetch } = useCreations({ userId, repository });
51
+ const creations = creationsData as Creation[] | undefined;
52
+ const deleteMutation = useDeleteCreation({ userId, repository });
53
+ const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } = useCreationsFilter({ creations });
54
+
55
+ // Refetch creations when screen comes into focus
56
+ useFocusEffect(
57
+ useCallback(() => {
58
+ refetch();
59
+ }, [refetch])
60
+ );
61
+
62
+ // Prepare data for UI using utils
63
+ const translatedTypes = useMemo(() => getTranslatedTypes(config, t), [config, t]);
64
+ const allCategories = useMemo(() => getFilterCategoriesFromConfig(config, t), [config, t]);
65
+
66
+ const handleShare = useCallback(async (creation: Creation) => {
67
+ share(creation.uri, { dialogTitle: t("common.share") });
68
+ }, [share, t]);
69
+
70
+ const handleDelete = useCallback(async (creation: Creation) => {
71
+ alert.show({
72
+ title: t(config.translations.deleteTitle),
73
+ message: t(config.translations.deleteMessage),
74
+ type: 'warning' as any,
75
+ actions: [
76
+ { id: 'cancel', label: t("common.cancel"), onPress: () => { } },
77
+ {
78
+ id: 'delete', label: t("common.delete"), variant: 'danger' as any, onPress: async () => {
79
+ const success = await deleteMutation.mutateAsync(creation.id);
80
+ if (success) setSelectedCreation(null);
81
+ }
82
+ }
83
+ ]
84
+ });
85
+ }, [alert, config, deleteMutation, t]);
86
+
87
+ // Handle viewing a creation - shows detail screen
88
+ const handleView = useCallback((creation: Creation) => {
89
+ setSelectedCreation(creation);
90
+ }, []);
91
+
92
+ const styles = useStyles(tokens);
93
+
94
+ // Define empty state content based on state
95
+ const renderEmptyComponent = useMemo(() => {
96
+ // 1. Loading State
97
+ if (isLoading && (!creations || creations?.length === 0)) {
98
+ return (
99
+ <View style={styles.centerContainer}>
100
+ <ActivityIndicator size="large" color={tokens.colors.primary} />
101
+ </View>
102
+ );
103
+ }
104
+
105
+ // 2. System Empty State (User has NO creations at all)
106
+ // We check 'creations' (the full list)
107
+ if (!creations || creations?.length === 0) {
108
+ return (
109
+ <View style={styles.centerContainer}>
110
+ <EmptyState
111
+ title={t(config.translations.empty)}
112
+ description={t(config.translations.emptyDescription)}
113
+ actionLabel={emptyActionLabel}
114
+ onAction={onEmptyAction}
115
+ />
116
+ </View>
117
+ );
118
+ }
119
+
120
+ // 3. Filter Empty State (User has creations, but filter returns none)
121
+ // We check 'filtered' (the displayed list)
122
+ return (
123
+ <View style={styles.centerContainer}>
124
+ <EmptyState
125
+ title={t("common.no_results") || "No results"}
126
+ description={t("common.no_results_description") || "Try changing your filters"}
127
+ actionLabel={t("common.clear_all") || "Clear All"}
128
+ onAction={clearFilters}
129
+ />
130
+ </View>
131
+ );
132
+ }, [isLoading, creations, config, t, emptyActionLabel, onEmptyAction, clearFilters, styles.centerContainer, tokens.colors.primary]);
133
+
134
+ if (selectedCreation) {
135
+ return (
136
+ <CreationDetailScreen
137
+ creation={selectedCreation}
138
+ onClose={() => setSelectedCreation(null)}
139
+ onShare={handleShare}
140
+ onDelete={handleDelete}
141
+ t={t}
142
+ />
143
+ );
144
+ }
145
+
146
+ return (
147
+ <View style={styles.container}>
148
+ {/* Header is always shown unless we are in "System Empty" without data?
149
+ User requested: "herhangi bir creations yoksa buradaki no creations gözükmeli" (if no creations, show no creations).
150
+ Currently we show header always, except logic below might hide it.
151
+ Actually, let's keep header always visible IF we have creations.
152
+ If !creations, we pass `renderEmptyComponent`, but Grid has header support.
153
+
154
+ However, to match previous request "filter gözükebilir" (filter can be visible), we'll keep header outside.
155
+ BUT, if NO creations, showing filter header is weird.
156
+
157
+ Let's conditonally render header: Only if we have creations OR loading.
158
+ If loaded and 0 creations -> Hide header (Clean Empty State).
159
+ */}
160
+ {(!creations || creations?.length === 0) && !isLoading ? null : (
161
+ <GalleryHeader
162
+ title={t(config.translations.title) || 'My Creations'}
163
+ count={filtered.length}
164
+ countLabel={t(config.translations.photoCount) || 'photos'}
165
+ isFiltered={isFiltered}
166
+ filterLabel={t(config.translations.filterLabel) || 'Filter'}
167
+ onFilterPress={() => filterSheetRef.current?.present()}
168
+ style={{ paddingTop: insets.top + tokens.spacing.md }}
169
+ />
170
+ )}
171
+
172
+ {/* Main Content Grid - handles empty/loading via ListEmptyComponent */}
173
+ <CreationsGrid
174
+ creations={filtered}
175
+ types={translatedTypes}
176
+ isLoading={isLoading}
177
+ onRefresh={refetch}
178
+ onView={handleView}
179
+ onShare={handleShare}
180
+ onDelete={handleDelete}
181
+ contentContainerStyle={{ paddingBottom: insets.bottom + tokens.spacing.xl }}
182
+ ListEmptyComponent={renderEmptyComponent}
183
+ />
184
+
185
+ <CreationImageViewer
186
+ creations={filtered}
187
+ visible={viewerVisible}
188
+ index={viewerIndex}
189
+ onDismiss={() => setViewerVisible(false)}
190
+ onIndexChange={setViewerIndex}
191
+ enableEditing={enableEditing}
192
+ onImageEdit={onImageEdit}
193
+ selectedCreationId={(selectedCreation as Creation | null)?.id}
194
+ />
195
+
196
+ <FilterBottomSheet
197
+ ref={filterSheetRef}
198
+ categories={allCategories}
199
+ selectedIds={selectedIds}
200
+ onFilterPress={(id, catId) => toggleFilter(id, allCategories.find(c => c.id === catId)?.multiSelect)}
201
+ onClearFilters={clearFilters}
202
+ title={t(config.translations.filterTitle) || t("common.filter")}
203
+ />
204
+ </View>
205
+ );
206
+ }
207
+
208
+ const useStyles = (tokens: any) => StyleSheet.create({
209
+ container: { flex: 1, backgroundColor: tokens.colors.background },
210
+ centerContainer: {
211
+ flex: 1,
212
+ justifyContent: 'center',
213
+ alignItems: 'center',
214
+ minHeight: 400,
215
+ paddingHorizontal: tokens.spacing.xl
216
+ },
217
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Presentation Screens
3
+ */
4
+
5
+ export { CreationsGalleryScreen } from "./CreationsGalleryScreen";
@@ -0,0 +1,52 @@
1
+ import { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
2
+ import { FilterCategory } from "../components/FilterBottomSheet";
3
+
4
+ /**
5
+ * Transforms the creations configuration into filter categories for the UI.
6
+ *
7
+ * @param config The creations configuration object
8
+ * @param t Translation function
9
+ * @returns Array of FilterCategory
10
+ */
11
+ export const getFilterCategoriesFromConfig = (
12
+ config: CreationsConfig,
13
+ t: (key: string) => string
14
+ ): FilterCategory[] => {
15
+ const categories: FilterCategory[] = [];
16
+
17
+ if (config.types.length > 0) {
18
+ categories.push({
19
+ id: 'type',
20
+ title: t(config.translations.filterTitle),
21
+ multiSelect: false,
22
+ options: config.types.map(type => ({
23
+ id: type.id,
24
+ label: t(type.labelKey),
25
+ icon: type.icon || 'image'
26
+ }))
27
+ });
28
+ }
29
+
30
+ if (config.filterCategories) {
31
+ categories.push(...config.filterCategories);
32
+ }
33
+
34
+ return categories;
35
+ };
36
+
37
+ /**
38
+ * Translates the creation types for display.
39
+ *
40
+ * @param config The creations configuration object
41
+ * @param t Translation function
42
+ * @returns Array of types with translated labels
43
+ */
44
+ export const getTranslatedTypes = (
45
+ config: CreationsConfig,
46
+ t: (key: string) => string
47
+ ) => {
48
+ return config.types.map(type => ({
49
+ ...type,
50
+ labelKey: t(type.labelKey)
51
+ }));
52
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Type declarations for external modules
3
+ */
4
+
5
+ declare module "@umituz/react-native-firestore" {
6
+ import type { Firestore } from "firebase/firestore";
7
+
8
+ export class BaseRepository {
9
+ protected getDb(): Firestore | null;
10
+ protected getDbOrThrow(): Firestore;
11
+ protected isDbInitialized(): boolean;
12
+ protected isQuotaError(error: unknown): boolean;
13
+ protected handleQuotaError(error: unknown): never;
14
+ protected executeWithQuotaHandling<T>(
15
+ operation: () => Promise<T>,
16
+ ): Promise<T>;
17
+ protected trackRead(
18
+ collection: string,
19
+ count: number,
20
+ cached: boolean,
21
+ ): void;
22
+ protected trackWrite(
23
+ collection: string,
24
+ docId: string,
25
+ count: number,
26
+ ): void;
27
+ protected trackDelete(
28
+ collection: string,
29
+ docId: string,
30
+ count: number,
31
+ ): void;
32
+ destroy(): void;
33
+ }
34
+
35
+ export function getFirestore(): Firestore | null;
36
+ export function initializeFirestore(app: unknown): void;
37
+ export function isFirestoreInitialized(): boolean;
38
+ }
39
+
40
+ declare module "@umituz/react-native-design-system" {
41
+ import type { FC, ReactNode } from "react";
42
+ import type { StyleProp, ViewStyle, TextStyle } from "react-native";
43
+
44
+ export interface DesignTokens {
45
+ colors: {
46
+ primary: string;
47
+ secondary: string;
48
+ error: string;
49
+ warning: string;
50
+ success: string;
51
+ backgroundPrimary: string;
52
+ backgroundSecondary: string;
53
+ surface: string;
54
+ textPrimary: string;
55
+ textSecondary: string;
56
+ textInverse: string;
57
+ border: string;
58
+ [key: string]: string;
59
+ };
60
+ spacing: {
61
+ xs: number;
62
+ sm: number;
63
+ md: number;
64
+ lg: number;
65
+ xl: number;
66
+ xxl: number;
67
+ [key: string]: number;
68
+ };
69
+ typography: {
70
+ headingLarge: TextStyle;
71
+ headingMedium: TextStyle;
72
+ headingSmall: TextStyle;
73
+ bodyLarge: TextStyle;
74
+ bodyMedium: TextStyle;
75
+ bodySmall: TextStyle;
76
+ [key: string]: TextStyle;
77
+ };
78
+ }
79
+
80
+ export function useAppDesignTokens(): DesignTokens;
81
+
82
+ export interface AtomicTextProps {
83
+ children?: ReactNode;
84
+ style?: StyleProp<TextStyle>;
85
+ }
86
+ export const AtomicText: FC<AtomicTextProps>;
87
+
88
+ export interface AtomicIconProps {
89
+ name: string;
90
+ size?: "xs" | "sm" | "md" | "lg" | "xl";
91
+ color?: string;
92
+ }
93
+ export const AtomicIcon: FC<AtomicIconProps>;
94
+ }
95
+
96
+ declare module "@umituz/react-native-sharing" {
97
+ export interface ShareOptions {
98
+ dialogTitle?: string;
99
+ mimeType?: string;
100
+ }
101
+
102
+ export interface UseSharingResult {
103
+ share: (uri: string, options?: ShareOptions) => Promise<void>;
104
+ }
105
+
106
+ export function useSharing(): UseSharingResult;
107
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Face Detection Constants
3
+ */
4
+
5
+ export const FACE_DETECTION_CONFIG = {
6
+ minConfidence: 0.5,
7
+ } as const;
8
+
9
+ export const FACE_DETECTION_PROMPTS = {
10
+ analyze: `Look at this image and check if there is a human face visible.
11
+ Reply with ONLY this JSON (no markdown, no explanation):
12
+ {"hasFace": true, "confidence": 0.9, "reason": "face visible"}
13
+
14
+ If no face is visible:
15
+ {"hasFace": false, "confidence": 0.1, "reason": "no face found"}`,
16
+ } as const;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Face Detection Types
3
+ */
4
+
5
+ export interface FaceDetectionResult {
6
+ hasFace: boolean;
7
+ confidence: number;
8
+ message: string;
9
+ }
10
+
11
+ export interface FaceValidationState {
12
+ isValidating: boolean;
13
+ result: FaceDetectionResult | null;
14
+ error: string | null;
15
+ }
16
+
17
+ export interface FaceDetectionConfig {
18
+ minConfidence: number;
19
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * React Native AI Face Detection - Public API
3
+ *
4
+ * AI-powered face detection for React Native apps using Gemini Vision
5
+ */
6
+
7
+ export type {
8
+ FaceDetectionResult,
9
+ FaceValidationState,
10
+ FaceDetectionConfig,
11
+ } from "./domain/entities/FaceDetection";
12
+
13
+ export { FACE_DETECTION_CONFIG, FACE_DETECTION_PROMPTS } from "./domain/constants/faceDetectionConstants";
14
+
15
+ export {
16
+ isValidFace,
17
+ parseDetectionResponse,
18
+ createFailedResult,
19
+ createSuccessResult,
20
+ } from "./infrastructure/validators/faceValidator";
21
+
22
+ export { analyzeImageForFace } from "./infrastructure/analyzers/faceAnalyzer";
23
+
24
+ export { useFaceDetection } from "./presentation/hooks/useFaceDetection";
25
+
26
+ export { FaceValidationStatus } from "./presentation/components/FaceValidationStatus";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Face Analyzer
3
+ *
4
+ * Analyzes images for face presence using Gemini AI.
5
+ */
6
+
7
+ import { geminiProviderService } from "@umituz/react-native-ai-gemini-provider";
8
+ import type { FaceDetectionResult } from "../../domain/entities/FaceDetection";
9
+ import { FACE_DETECTION_PROMPTS } from "../../domain/constants/faceDetectionConstants";
10
+ import {
11
+ parseDetectionResponse,
12
+ createFailedResult,
13
+ } from "../validators/faceValidator";
14
+
15
+ export const analyzeImageForFace = async (
16
+ base64Image: string,
17
+ ): Promise<FaceDetectionResult> => {
18
+ try {
19
+ const result = await geminiProviderService.run<{ text: string }>(
20
+ "gemini-2.0-flash-exp",
21
+ {
22
+ prompt: FACE_DETECTION_PROMPTS.analyze,
23
+ image_url: base64Image,
24
+ },
25
+ );
26
+
27
+ if (!result.text) {
28
+ return createFailedResult("No response from AI");
29
+ }
30
+
31
+ return parseDetectionResponse(result.text);
32
+ } catch (error) {
33
+ const message = error instanceof Error ? error.message : "Analysis failed";
34
+ return createFailedResult(message);
35
+ }
36
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Face Validator
3
+ *
4
+ * Pure functions for validating face detection results.
5
+ */
6
+
7
+ import type { FaceDetectionResult } from "../../domain/entities/FaceDetection";
8
+ import { FACE_DETECTION_CONFIG } from "../../domain/constants/faceDetectionConstants";
9
+
10
+ export const isValidFace = (result: FaceDetectionResult): boolean => {
11
+ return (
12
+ result.hasFace && result.confidence >= FACE_DETECTION_CONFIG.minConfidence
13
+ );
14
+ };
15
+
16
+ export const parseDetectionResponse = (
17
+ response: string,
18
+ ): FaceDetectionResult => {
19
+ try {
20
+ let cleaned = response.replace(/```json\n?/g, "").replace(/```\n?/g, "");
21
+ cleaned = cleaned.trim();
22
+
23
+ const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
24
+ if (!jsonMatch) {
25
+ return createFailedResult("Invalid response format");
26
+ }
27
+
28
+ const parsed = JSON.parse(jsonMatch[0]);
29
+
30
+ return {
31
+ hasFace: Boolean(parsed.hasFace),
32
+ confidence: Number(parsed.confidence) || 0,
33
+ message: String(parsed.reason || ""),
34
+ };
35
+ } catch (error) {
36
+ return createFailedResult("Failed to parse response");
37
+ }
38
+ };
39
+
40
+ export const createFailedResult = (message: string): FaceDetectionResult => ({
41
+ hasFace: false,
42
+ confidence: 0,
43
+ message,
44
+ });
45
+
46
+ export const createSuccessResult = (
47
+ confidence: number,
48
+ ): FaceDetectionResult => ({
49
+ hasFace: true,
50
+ confidence,
51
+ message: "Face detected successfully",
52
+ });