@umituz/react-native-design-system 2.8.8 → 2.8.9
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 +6 -1
- package/src/exports/media.ts +1 -0
- package/src/index.ts +5 -0
- package/src/media/domain/entities/CardMultimedia.types.README.md +129 -0
- package/src/media/domain/entities/CardMultimedia.types.ts +105 -0
- package/src/media/domain/entities/Media.README.md +80 -0
- package/src/media/domain/entities/Media.ts +139 -0
- package/src/media/domain/entities/MultimediaFlashcardTypes.README.md +144 -0
- package/src/media/domain/entities/MultimediaFlashcardTypes.ts +105 -0
- package/src/media/domain/utils/MediaUtils.README.md +178 -0
- package/src/media/domain/utils/MediaUtils.ts +82 -0
- package/src/media/index.ts +70 -0
- package/src/media/index.ts.README.md +191 -0
- package/src/media/infrastructure/services/CardMediaGenerationService.README.md +99 -0
- package/src/media/infrastructure/services/CardMediaGenerationService.ts +101 -0
- package/src/media/infrastructure/services/CardMediaOptimizerService.README.md +167 -0
- package/src/media/infrastructure/services/CardMediaOptimizerService.ts +36 -0
- package/src/media/infrastructure/services/CardMediaUploadService.README.md +123 -0
- package/src/media/infrastructure/services/CardMediaUploadService.ts +67 -0
- package/src/media/infrastructure/services/CardMediaValidationService.README.md +134 -0
- package/src/media/infrastructure/services/CardMediaValidationService.ts +81 -0
- package/src/media/infrastructure/services/CardMultimediaService.README.md +176 -0
- package/src/media/infrastructure/services/CardMultimediaService.ts +97 -0
- package/src/media/infrastructure/services/MediaGenerationService.README.md +142 -0
- package/src/media/infrastructure/services/MediaGenerationService.ts +80 -0
- package/src/media/infrastructure/services/MediaOptimizerService.README.md +145 -0
- package/src/media/infrastructure/services/MediaOptimizerService.ts +32 -0
- package/src/media/infrastructure/services/MediaPickerService.README.md +106 -0
- package/src/media/infrastructure/services/MediaPickerService.ts +173 -0
- package/src/media/infrastructure/services/MediaSaveService.README.md +120 -0
- package/src/media/infrastructure/services/MediaSaveService.ts +154 -0
- package/src/media/infrastructure/services/MediaUploadService.README.md +135 -0
- package/src/media/infrastructure/services/MediaUploadService.ts +62 -0
- package/src/media/infrastructure/services/MediaValidationService.README.md +135 -0
- package/src/media/infrastructure/services/MediaValidationService.ts +61 -0
- package/src/media/infrastructure/services/MultimediaFlashcardService.README.md +142 -0
- package/src/media/infrastructure/services/MultimediaFlashcardService.ts +95 -0
- package/src/media/infrastructure/utils/mediaHelpers.README.md +96 -0
- package/src/media/infrastructure/utils/mediaHelpers.ts +82 -0
- package/src/media/infrastructure/utils/mediaPickerMappers.README.md +129 -0
- package/src/media/infrastructure/utils/mediaPickerMappers.ts +76 -0
- package/src/media/presentation/hooks/card-multimedia.types.README.md +177 -0
- package/src/media/presentation/hooks/card-multimedia.types.ts +51 -0
- package/src/media/presentation/hooks/multimedia.types.README.md +201 -0
- package/src/media/presentation/hooks/multimedia.types.ts +51 -0
- package/src/media/presentation/hooks/useCardMediaGeneration.README.md +164 -0
- package/src/media/presentation/hooks/useCardMediaGeneration.ts +124 -0
- package/src/media/presentation/hooks/useCardMediaUpload.README.md +153 -0
- package/src/media/presentation/hooks/useCardMediaUpload.ts +86 -0
- package/src/media/presentation/hooks/useCardMediaValidation.README.md +176 -0
- package/src/media/presentation/hooks/useCardMediaValidation.ts +101 -0
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.README.md +158 -0
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +104 -0
- package/src/media/presentation/hooks/useMedia.README.md +94 -0
- package/src/media/presentation/hooks/useMedia.ts +186 -0
- package/src/media/presentation/hooks/useMediaGeneration.README.md +118 -0
- package/src/media/presentation/hooks/useMediaGeneration.ts +101 -0
- package/src/media/presentation/hooks/useMediaUpload.README.md +108 -0
- package/src/media/presentation/hooks/useMediaUpload.ts +86 -0
- package/src/media/presentation/hooks/useMediaValidation.README.md +134 -0
- package/src/media/presentation/hooks/useMediaValidation.ts +93 -0
- package/src/media/presentation/hooks/useMultimediaFlashcard.README.md +141 -0
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +103 -0
- package/src/storage/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +1 -1
- package/src/storage/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card Multimedia Flashcard Hooks
|
|
3
|
+
* Main hook and exports for card multimedia functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import {
|
|
8
|
+
calculateTotalSize,
|
|
9
|
+
extractMediaTypes,
|
|
10
|
+
} from "../../infrastructure/utils/mediaHelpers";
|
|
11
|
+
import type { UseCardMultimediaFlashcardResult } from "./card-multimedia.types";
|
|
12
|
+
import type {
|
|
13
|
+
CardMediaAttachment,
|
|
14
|
+
CardMultimediaFlashcard,
|
|
15
|
+
} from "../../domain/entities/CardMultimedia.types";
|
|
16
|
+
|
|
17
|
+
// Export individual hooks
|
|
18
|
+
export { useCardMediaUpload } from "./useCardMediaUpload";
|
|
19
|
+
export { useCardMediaGeneration } from "./useCardMediaGeneration";
|
|
20
|
+
export { useCardMediaValidation } from "./useCardMediaValidation";
|
|
21
|
+
|
|
22
|
+
// Export types
|
|
23
|
+
export type {
|
|
24
|
+
UseCardMediaUploadResult,
|
|
25
|
+
UseCardMediaGenerationResult,
|
|
26
|
+
UseCardMediaValidationResult,
|
|
27
|
+
UseCardMultimediaFlashcardResult,
|
|
28
|
+
} from "./card-multimedia.types";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Main hook for card multimedia flashcard operations
|
|
32
|
+
*/
|
|
33
|
+
export const useCardMultimediaFlashcard =
|
|
34
|
+
(): UseCardMultimediaFlashcardResult => {
|
|
35
|
+
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
36
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
const createCardMultimedia = React.useCallback(
|
|
39
|
+
async (cardData: any): Promise<CardMultimediaFlashcard> => {
|
|
40
|
+
try {
|
|
41
|
+
setIsProcessing(true);
|
|
42
|
+
setError(null);
|
|
43
|
+
|
|
44
|
+
// Simulate card creation
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
46
|
+
|
|
47
|
+
const card: CardMultimediaFlashcard = {
|
|
48
|
+
id: `card_multimedia_${Date.now()}`,
|
|
49
|
+
front: cardData.front || "",
|
|
50
|
+
back: cardData.back || "",
|
|
51
|
+
difficulty: cardData.difficulty || "medium",
|
|
52
|
+
tags: cardData.tags || [],
|
|
53
|
+
media: cardData.media || [],
|
|
54
|
+
hasMedia: (cardData.media || []).length > 0,
|
|
55
|
+
mediaType: extractMediaTypes(cardData.media || []),
|
|
56
|
+
isDownloaded: (cardData.media || []).every(
|
|
57
|
+
(m: any) => m.isDownloaded,
|
|
58
|
+
),
|
|
59
|
+
estimatedSize: calculateTotalSize(cardData.media || []),
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return card;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const errorMessage =
|
|
66
|
+
err instanceof Error ? err.message : "Card creation failed";
|
|
67
|
+
setError(errorMessage);
|
|
68
|
+
setIsProcessing(false);
|
|
69
|
+
throw err;
|
|
70
|
+
} finally {
|
|
71
|
+
setIsProcessing(false);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const updateCardMedia = React.useCallback(
|
|
78
|
+
async (
|
|
79
|
+
cardId: string,
|
|
80
|
+
media: CardMediaAttachment[],
|
|
81
|
+
): Promise<CardMultimediaFlashcard> => {
|
|
82
|
+
// Mock implementation
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
84
|
+
return {} as CardMultimediaFlashcard;
|
|
85
|
+
},
|
|
86
|
+
[],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const deleteCardMedia = React.useCallback(
|
|
90
|
+
async (attachmentId: string): Promise<void> => {
|
|
91
|
+
// Mock implementation
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
93
|
+
},
|
|
94
|
+
[],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
createCardMultimedia,
|
|
99
|
+
updateCardMedia,
|
|
100
|
+
deleteCardMedia,
|
|
101
|
+
isProcessing,
|
|
102
|
+
error,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# useMedia
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Core React hook for media selection operations (image/video picking, camera access) in React Native applications.
|
|
5
|
+
|
|
6
|
+
## File Location
|
|
7
|
+
`src/presentation/hooks/useMedia.ts`
|
|
8
|
+
|
|
9
|
+
## Strategy
|
|
10
|
+
- Provide unified interface for media picker and camera operations
|
|
11
|
+
- Abstract expo-image-picker complexity from components
|
|
12
|
+
- Centralize permission management logic
|
|
13
|
+
- Maintain consistent state management across media operations
|
|
14
|
+
- Enable type-safe media operations with proper TypeScript interfaces
|
|
15
|
+
- Support single and multiple media selection workflows
|
|
16
|
+
- Handle loading states and error propagation
|
|
17
|
+
|
|
18
|
+
## Forbidden
|
|
19
|
+
- **DO NOT** expose expo-image-picker implementation details to consumers
|
|
20
|
+
- **DO NOT** automatically request permissions without explicit user action
|
|
21
|
+
- **DO NOT** mix camera and library picker logic in single operations
|
|
22
|
+
- **DO NOT** assume permission states - always check before operations
|
|
23
|
+
- **DO NOT** bypass error handling or loading states
|
|
24
|
+
- **DO NOT** use mock implementations in production without clear warnings
|
|
25
|
+
- **DO NOT** allow operations while previous operation is in progress
|
|
26
|
+
- **DO NOT** modify media assets after selection
|
|
27
|
+
- **DO NOT** store selected media permanently in hook state
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
1. Always check permission status before camera/library operations
|
|
31
|
+
2. All picker operations must return MediaPickerResult with canceled flag
|
|
32
|
+
3. Loading state must be true during async operations
|
|
33
|
+
4. Error state must be cleared on new operation attempts
|
|
34
|
+
5. Permission requests must be explicit, not automatic
|
|
35
|
+
6. Selected media must be validated against type definitions
|
|
36
|
+
7. Quality parameters must be in 0-1 range
|
|
37
|
+
8. Aspect ratio must be [width, height] tuple
|
|
38
|
+
9. File size must be included when available from picker
|
|
39
|
+
10. Support both single and multiple selection based on function used
|
|
40
|
+
|
|
41
|
+
## AI Agent Guidelines
|
|
42
|
+
|
|
43
|
+
When working with useMedia hook:
|
|
44
|
+
|
|
45
|
+
1. **Permission First**: Always request/check permissions before calling picker or camera functions
|
|
46
|
+
2. **State Management**: Use isLoading to prevent duplicate operations during active calls
|
|
47
|
+
3. **Error Handling**: Always check error state and handle user cancellation (canceled: true)
|
|
48
|
+
4. **Type Safety**: Use MediaPickerOptions and CameraOptions interfaces for configuration
|
|
49
|
+
5. **Validation**: Validate returned assets before processing (check if assets array exists)
|
|
50
|
+
|
|
51
|
+
### Key Functions
|
|
52
|
+
|
|
53
|
+
- **pickImage**: Single image selection from library with optional editing
|
|
54
|
+
- **pickMultipleImages**: Multiple image selection with selection limit
|
|
55
|
+
- **pickVideo**: Video selection from library
|
|
56
|
+
- **launchCamera**: Photo capture via device camera
|
|
57
|
+
- **launchCameraForVideo**: Video recording via device camera
|
|
58
|
+
- **requestCameraPermission**: Request camera access permission
|
|
59
|
+
- **requestMediaLibraryPermission**: Request photo library access permission
|
|
60
|
+
- **getCameraPermissionStatus**: Check current camera permission state
|
|
61
|
+
- **getMediaLibraryPermissionStatus**: Check current library permission state
|
|
62
|
+
|
|
63
|
+
### Media Selection Workflow
|
|
64
|
+
|
|
65
|
+
1. Check permission status before operation
|
|
66
|
+
2. Request permission if not granted
|
|
67
|
+
3. Call appropriate picker/camera function with options
|
|
68
|
+
4. Check canceled flag before processing assets
|
|
69
|
+
5. Validate assets array exists and has items
|
|
70
|
+
6. Process first asset (or all for multiple selection)
|
|
71
|
+
|
|
72
|
+
### Quality Guidelines
|
|
73
|
+
|
|
74
|
+
- HIGH (1.0): Original quality, largest file size
|
|
75
|
+
- MEDIUM (0.7): Balanced quality and size (recommended)
|
|
76
|
+
- LOW (0.3): Smallest file size, reduced quality
|
|
77
|
+
|
|
78
|
+
### Platform Requirements
|
|
79
|
+
|
|
80
|
+
- **iOS**: Add NSCameraUsageDescription and NSPhotoLibraryUsageDescription to Info.plist
|
|
81
|
+
- **Android**: Add CAMERA and READ_EXTERNAL_STORAGE permissions to AndroidManifest.xml
|
|
82
|
+
|
|
83
|
+
### State Management
|
|
84
|
+
|
|
85
|
+
- **isLoading**: True during any async picker/camera operation
|
|
86
|
+
- **error**: String error message or null, cleared on new operations
|
|
87
|
+
- **MediaPickerResult.canceled**: True when user cancels operation
|
|
88
|
+
- **MediaPickerResult.assets**: Array of selected media or undefined
|
|
89
|
+
|
|
90
|
+
## Dependencies
|
|
91
|
+
|
|
92
|
+
- MediaPickerService (infrastructure layer)
|
|
93
|
+
- Domain types: MediaAsset, MediaPickerResult, MediaPickerOptions, CameraOptions
|
|
94
|
+
- expo-image-picker (via service layer)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Domain - useMedia Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for media picking operations (images, videos).
|
|
5
|
+
* Provides camera, gallery picking functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import { MediaPickerService } from "../../infrastructure/services/MediaPickerService";
|
|
10
|
+
import type {
|
|
11
|
+
MediaPickerOptions,
|
|
12
|
+
MediaPickerResult,
|
|
13
|
+
CameraOptions,
|
|
14
|
+
} from "../../domain/entities/Media";
|
|
15
|
+
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* useMedia hook for complete media workflow
|
|
19
|
+
*
|
|
20
|
+
* USAGE:
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const {
|
|
23
|
+
* pickImage,
|
|
24
|
+
* pickMultipleImages,
|
|
25
|
+
* launchCamera,
|
|
26
|
+
* isLoading,
|
|
27
|
+
* error,
|
|
28
|
+
* } = useMedia();
|
|
29
|
+
*
|
|
30
|
+
* const handlePickImage = async () => {
|
|
31
|
+
* const result = await pickImage({ allowsEditing: true });
|
|
32
|
+
* if (!result.canceled && result.assets) {
|
|
33
|
+
* console.log('Picked:', result.assets[0].uri);
|
|
34
|
+
* }
|
|
35
|
+
* };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export const useMedia = () => {
|
|
39
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
40
|
+
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
const pickImage = useCallback(
|
|
43
|
+
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
44
|
+
setIsLoading(true);
|
|
45
|
+
setError(null);
|
|
46
|
+
try {
|
|
47
|
+
const result = await MediaPickerService.pickSingleImage(options);
|
|
48
|
+
return result;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const errorMessage =
|
|
51
|
+
err instanceof Error ? err.message : "Failed to pick image";
|
|
52
|
+
setError(errorMessage);
|
|
53
|
+
return { canceled: true };
|
|
54
|
+
} finally {
|
|
55
|
+
setIsLoading(false);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const pickMultipleImages = useCallback(
|
|
62
|
+
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
63
|
+
setIsLoading(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
try {
|
|
66
|
+
const result = await MediaPickerService.pickMultipleImages(options);
|
|
67
|
+
return result;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const errorMessage =
|
|
70
|
+
err instanceof Error ? err.message : "Failed to pick images";
|
|
71
|
+
setError(errorMessage);
|
|
72
|
+
return { canceled: true };
|
|
73
|
+
} finally {
|
|
74
|
+
setIsLoading(false);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const pickVideo = useCallback(
|
|
81
|
+
async (options?: MediaPickerOptions): Promise<MediaPickerResult> => {
|
|
82
|
+
setIsLoading(true);
|
|
83
|
+
setError(null);
|
|
84
|
+
try {
|
|
85
|
+
const result = await MediaPickerService.pickVideo(options);
|
|
86
|
+
return result;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const errorMessage =
|
|
89
|
+
err instanceof Error ? err.message : "Failed to pick video";
|
|
90
|
+
setError(errorMessage);
|
|
91
|
+
return { canceled: true };
|
|
92
|
+
} finally {
|
|
93
|
+
setIsLoading(false);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const launchCamera = useCallback(
|
|
100
|
+
async (options?: CameraOptions): Promise<MediaPickerResult> => {
|
|
101
|
+
setIsLoading(true);
|
|
102
|
+
setError(null);
|
|
103
|
+
try {
|
|
104
|
+
const result = await MediaPickerService.launchCamera(options);
|
|
105
|
+
return result;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const errorMessage =
|
|
108
|
+
err instanceof Error ? err.message : "Failed to launch camera";
|
|
109
|
+
setError(errorMessage);
|
|
110
|
+
return { canceled: true };
|
|
111
|
+
} finally {
|
|
112
|
+
setIsLoading(false);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const launchCameraForVideo = useCallback(
|
|
119
|
+
async (options?: CameraOptions): Promise<MediaPickerResult> => {
|
|
120
|
+
setIsLoading(true);
|
|
121
|
+
setError(null);
|
|
122
|
+
try {
|
|
123
|
+
const result = await MediaPickerService.launchCameraForVideo(options);
|
|
124
|
+
return result;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const errorMessage =
|
|
127
|
+
err instanceof Error ? err.message : "Failed to record video";
|
|
128
|
+
setError(errorMessage);
|
|
129
|
+
return { canceled: true };
|
|
130
|
+
} finally {
|
|
131
|
+
setIsLoading(false);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
[]
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const requestCameraPermission =
|
|
138
|
+
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
139
|
+
try {
|
|
140
|
+
return await MediaPickerService.requestCameraPermission();
|
|
141
|
+
} catch {
|
|
142
|
+
return MediaLibraryPermission.DENIED;
|
|
143
|
+
}
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const requestMediaLibraryPermission =
|
|
147
|
+
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
148
|
+
try {
|
|
149
|
+
return await MediaPickerService.requestMediaLibraryPermission();
|
|
150
|
+
} catch {
|
|
151
|
+
return MediaLibraryPermission.DENIED;
|
|
152
|
+
}
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
const getCameraPermissionStatus =
|
|
156
|
+
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
157
|
+
try {
|
|
158
|
+
return await MediaPickerService.getCameraPermissionStatus();
|
|
159
|
+
} catch {
|
|
160
|
+
return MediaLibraryPermission.DENIED;
|
|
161
|
+
}
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
const getMediaLibraryPermissionStatus =
|
|
165
|
+
useCallback(async (): Promise<MediaLibraryPermission> => {
|
|
166
|
+
try {
|
|
167
|
+
return await MediaPickerService.getMediaLibraryPermissionStatus();
|
|
168
|
+
} catch {
|
|
169
|
+
return MediaLibraryPermission.DENIED;
|
|
170
|
+
}
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
pickImage,
|
|
175
|
+
pickMultipleImages,
|
|
176
|
+
pickVideo,
|
|
177
|
+
launchCamera,
|
|
178
|
+
launchCameraForVideo,
|
|
179
|
+
requestCameraPermission,
|
|
180
|
+
requestMediaLibraryPermission,
|
|
181
|
+
getCameraPermissionStatus,
|
|
182
|
+
getMediaLibraryPermissionStatus,
|
|
183
|
+
isLoading,
|
|
184
|
+
error,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# useMediaGeneration
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
React hook for AI-powered media generation (text-to-image, text-to-audio) with result tracking.
|
|
5
|
+
|
|
6
|
+
## File Location
|
|
7
|
+
`src/presentation/hooks/useMediaGeneration.ts`
|
|
8
|
+
|
|
9
|
+
## Strategy
|
|
10
|
+
- Provide unified interface for AI media generation operations
|
|
11
|
+
- Support multiple generation types (text-to-image, text-to-audio)
|
|
12
|
+
- Track generation status and progress
|
|
13
|
+
- Manage credit usage and balance tracking
|
|
14
|
+
- Handle generation errors gracefully
|
|
15
|
+
- Return structured results with metadata
|
|
16
|
+
- Support customizable generation options
|
|
17
|
+
|
|
18
|
+
## Forbidden
|
|
19
|
+
- **DO NOT** start new generation while previous is in progress
|
|
20
|
+
- **DO NOT** ignore credit costs before generation
|
|
21
|
+
- **DO NOT** mock generation process in production without API integration
|
|
22
|
+
- **DO NOT** assume generation will succeed - always check result.success
|
|
23
|
+
- **DO NOT** expose API keys or AI service implementation details
|
|
24
|
+
- **DO NOT** allow unlimited concurrent generations
|
|
25
|
+
- **DO NOT** bypass generation timeout handling
|
|
26
|
+
- **DO NOT** store large generation results permanently in hook state
|
|
27
|
+
- **DO NOT** use empty or invalid prompts for generation
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
1. Always validate prompt text before generation
|
|
31
|
+
2. Check credit availability before generation operations
|
|
32
|
+
3. Track processing time for each generation request
|
|
33
|
+
4. Return unique requestId for each generation
|
|
34
|
+
5. Support maxResults parameter for multiple outputs
|
|
35
|
+
6. Generation state must be cleared on new operations
|
|
36
|
+
7. Include creditsUsed in result metadata
|
|
37
|
+
8. Handle both success and failure in result object
|
|
38
|
+
9. Support language and voice options for audio generation
|
|
39
|
+
10. Support style options for image generation
|
|
40
|
+
|
|
41
|
+
## AI Agent Guidelines
|
|
42
|
+
|
|
43
|
+
When working with useMediaGeneration hook:
|
|
44
|
+
|
|
45
|
+
1. **Prompt Quality**: Use descriptive, specific prompts for better results
|
|
46
|
+
2. **Credit Management**: Track credit costs and balance before operations
|
|
47
|
+
3. **Error Handling**: Always check result.success field
|
|
48
|
+
4. **Result Processing**: Validate attachments array before use
|
|
49
|
+
5. **Options**: Use appropriate options for generation type
|
|
50
|
+
|
|
51
|
+
### Generation Types
|
|
52
|
+
|
|
53
|
+
**Text-to-Image** (type: 'text_to_image')
|
|
54
|
+
- Generates images from text descriptions
|
|
55
|
+
- Credit cost: 5 per generation
|
|
56
|
+
- Default maxResults: 1
|
|
57
|
+
- Supports style customization
|
|
58
|
+
|
|
59
|
+
**Text-to-Audio** (type: 'text_to_audio')
|
|
60
|
+
- Generates audio from text (text-to-speech)
|
|
61
|
+
- Credit cost: 3 per generation
|
|
62
|
+
- Default duration: 10 seconds
|
|
63
|
+
- Supports voice, language, speed options
|
|
64
|
+
|
|
65
|
+
### Generation Workflow
|
|
66
|
+
|
|
67
|
+
1. Validate prompt text quality and length
|
|
68
|
+
2. Check available credits
|
|
69
|
+
3. Configure generation options
|
|
70
|
+
4. Call generateMedia with request
|
|
71
|
+
5. Monitor isGenerating state
|
|
72
|
+
6. Process generationResult on completion
|
|
73
|
+
7. Handle errors with user feedback
|
|
74
|
+
|
|
75
|
+
### Result Structure
|
|
76
|
+
|
|
77
|
+
GenerationResult includes:
|
|
78
|
+
- success: Boolean indicating success/failure
|
|
79
|
+
- attachments: Array of generated MediaAttachment
|
|
80
|
+
- creditsUsed: Number of credits consumed
|
|
81
|
+
- processingTime: Duration in milliseconds
|
|
82
|
+
- requestId: Unique request identifier
|
|
83
|
+
- error: Error message if failed
|
|
84
|
+
|
|
85
|
+
### Generation Options
|
|
86
|
+
|
|
87
|
+
**Image Options:**
|
|
88
|
+
- maxResults: Number of images to generate (default: 1)
|
|
89
|
+
- style: Image style preset
|
|
90
|
+
|
|
91
|
+
**Audio Options:**
|
|
92
|
+
- voice: Voice type (male/female)
|
|
93
|
+
- language: Language code (e.g., 'tr-TR', 'en-US')
|
|
94
|
+
- speed: Playback speed (default: 1.0)
|
|
95
|
+
|
|
96
|
+
### Integration Requirements
|
|
97
|
+
|
|
98
|
+
- Configure AI API endpoints
|
|
99
|
+
- Implement authentication for AI services
|
|
100
|
+
- Handle rate limiting and quotas
|
|
101
|
+
- Implement retry logic for failed generations
|
|
102
|
+
- Cache generation results when appropriate
|
|
103
|
+
- Monitor credit balance and usage
|
|
104
|
+
|
|
105
|
+
### Error Scenarios
|
|
106
|
+
|
|
107
|
+
- Insufficient credits: Check balance before generation
|
|
108
|
+
- Invalid prompt: Validate text quality and length
|
|
109
|
+
- API timeout: Implement retry with exponential backoff
|
|
110
|
+
- Content policy violations: Filter and validate prompts
|
|
111
|
+
- Service unavailable: Graceful degradation with error message
|
|
112
|
+
|
|
113
|
+
## Dependencies
|
|
114
|
+
|
|
115
|
+
- MediaGenerationService (infrastructure layer)
|
|
116
|
+
- Domain types: MediaGenerationRequest, MediaGenerationResult, MediaAttachment
|
|
117
|
+
- Credit/balance tracking system
|
|
118
|
+
- AI service APIs (text-to-image, text-to-speech)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Generation Hook
|
|
3
|
+
* Hook for generating media with AI
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import type { UseMediaGenerationResult } from "./multimedia.types";
|
|
8
|
+
import type {
|
|
9
|
+
MediaAttachment,
|
|
10
|
+
MediaGenerationRequest,
|
|
11
|
+
MediaGenerationResult,
|
|
12
|
+
} from "../../domain/entities/MultimediaFlashcardTypes";
|
|
13
|
+
|
|
14
|
+
export const useMediaGeneration = (): UseMediaGenerationResult => {
|
|
15
|
+
const [isGenerating, setIsGenerating] = React.useState(false);
|
|
16
|
+
const [generationResult, setGenerationResult] =
|
|
17
|
+
React.useState<MediaGenerationResult | null>(null);
|
|
18
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
19
|
+
|
|
20
|
+
const generateMedia = React.useCallback(
|
|
21
|
+
async (request: MediaGenerationRequest): Promise<MediaGenerationResult> => {
|
|
22
|
+
try {
|
|
23
|
+
setIsGenerating(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
|
|
26
|
+
// Simulate generation
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
28
|
+
|
|
29
|
+
const attachments: MediaAttachment[] = [];
|
|
30
|
+
|
|
31
|
+
switch (request.type) {
|
|
32
|
+
case "text_to_image":
|
|
33
|
+
for (let i = 0; i < (request.options.maxResults || 1); i++) {
|
|
34
|
+
attachments.push({
|
|
35
|
+
id: `ai_img_${Date.now()}_${i}`,
|
|
36
|
+
type: "image",
|
|
37
|
+
position: "both",
|
|
38
|
+
url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
|
|
39
|
+
filename: `ai_generated_${i}.jpg`,
|
|
40
|
+
fileSize: 150000, // 150KB
|
|
41
|
+
mimeType: "image/jpeg",
|
|
42
|
+
isDownloaded: false,
|
|
43
|
+
createdAt: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case "text_to_audio":
|
|
49
|
+
attachments.push({
|
|
50
|
+
id: `ai_audio_${Date.now()}`,
|
|
51
|
+
type: "audio",
|
|
52
|
+
position: "back",
|
|
53
|
+
url: `https://example.com/audio_${Date.now()}.mp3`,
|
|
54
|
+
filename: `ai_generated_${Date.now()}.mp3`,
|
|
55
|
+
fileSize: 80000, // 80KB
|
|
56
|
+
mimeType: "audio/mp3",
|
|
57
|
+
duration: 10, // 10 seconds
|
|
58
|
+
isDownloaded: false,
|
|
59
|
+
createdAt: new Date().toISOString(),
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result: MediaGenerationResult = {
|
|
65
|
+
success: true,
|
|
66
|
+
attachments,
|
|
67
|
+
creditsUsed: request.type === "text_to_image" ? 5 : 3,
|
|
68
|
+
processingTime: 3000,
|
|
69
|
+
requestId: `req_${Date.now()}`,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
setGenerationResult(result);
|
|
73
|
+
return result;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const errorMessage =
|
|
76
|
+
err instanceof Error ? err.message : "Generation failed";
|
|
77
|
+
setError(errorMessage);
|
|
78
|
+
setIsGenerating(false);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
attachments: [],
|
|
83
|
+
creditsUsed: 0,
|
|
84
|
+
processingTime: 0,
|
|
85
|
+
error: errorMessage,
|
|
86
|
+
requestId: "",
|
|
87
|
+
};
|
|
88
|
+
} finally {
|
|
89
|
+
setIsGenerating(false);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
[],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
generateMedia,
|
|
97
|
+
isGenerating,
|
|
98
|
+
generationResult,
|
|
99
|
+
error,
|
|
100
|
+
};
|
|
101
|
+
};
|