@umituz/react-native-design-system 4.23.101 → 4.23.102
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 +1 -1
- package/src/media/index.ts +4 -1
- package/src/media/infrastructure/utils/PermissionManager.ts +1 -1
- package/src/media/infrastructure/utils/mediaPickerMappers.ts +1 -1
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +4 -4
- package/src/media/domain/entities/MediaAttachments.ts +0 -101
- package/src/media/infrastructure/services/MediaGenerationService.ts +0 -80
- package/src/media/infrastructure/services/MediaOptimizerService.ts +0 -32
- package/src/media/infrastructure/services/MediaPickerService.ts +0 -157
- package/src/media/infrastructure/services/MediaSaveService.ts +0 -97
- package/src/media/infrastructure/services/MediaUploadService.ts +0 -60
- package/src/media/infrastructure/services/MediaValidationService.ts +0 -61
- package/src/media/infrastructure/services/MultimediaFlashcardService.ts +0 -96
- package/src/media/presentation/hooks/multimedia.types.ts +0 -53
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +0 -102
- package/src/media/presentation/hooks/useMedia.ts +0 -190
- package/src/media/presentation/hooks/useMediaGeneration.ts +0 -18
- package/src/media/presentation/hooks/useMediaUpload.ts +0 -84
- package/src/media/presentation/hooks/useMediaValidation.ts +0 -93
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +0 -101
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.102",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/media/index.ts
CHANGED
|
@@ -106,7 +106,7 @@ export type {
|
|
|
106
106
|
CreateMultimediaCardData,
|
|
107
107
|
} from "./domain/entities/MediaAttachments";
|
|
108
108
|
|
|
109
|
-
// Media Hooks
|
|
109
|
+
// Media Attachment Hooks
|
|
110
110
|
export { useMediaUpload } from "./presentation/hooks/useMediaUpload";
|
|
111
111
|
export { useMediaGeneration } from "./presentation/hooks/useMediaGeneration";
|
|
112
112
|
export { useMediaValidation } from "./presentation/hooks/useMediaValidation";
|
|
@@ -117,3 +117,6 @@ export type {
|
|
|
117
117
|
UseMediaValidationResult,
|
|
118
118
|
UseMultimediaFlashcardResult,
|
|
119
119
|
} from "./presentation/hooks/multimedia.types";
|
|
120
|
+
|
|
121
|
+
// Media Attachment Services
|
|
122
|
+
export { MultimediaFlashcardService } from "./infrastructure/services/MultimediaFlashcardService";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as ImagePicker from "expo-image-picker";
|
|
8
|
-
import { MediaLibraryPermission } from "../../domain/entities/
|
|
8
|
+
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
9
9
|
import { mapPermissionStatus } from "./mediaPickerMappers";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -37,7 +37,7 @@ export class BaseStorageOperations {
|
|
|
37
37
|
return failure(new StorageDeserializationError(key, parseError), defaultValue);
|
|
38
38
|
}
|
|
39
39
|
} catch (_error) {
|
|
40
|
-
return failure(new StorageReadError(key,
|
|
40
|
+
return failure(new StorageReadError(key, _error), defaultValue);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -56,7 +56,7 @@ export class BaseStorageOperations {
|
|
|
56
56
|
await AsyncStorage.setItem(key, serialized);
|
|
57
57
|
return success(value);
|
|
58
58
|
} catch (_error) {
|
|
59
|
-
return failure(new StorageWriteError(key,
|
|
59
|
+
return failure(new StorageWriteError(key, _error));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -68,7 +68,7 @@ export class BaseStorageOperations {
|
|
|
68
68
|
await AsyncStorage.removeItem(key);
|
|
69
69
|
return success(undefined);
|
|
70
70
|
} catch (_error) {
|
|
71
|
-
return failure(new StorageDeleteError(key,
|
|
71
|
+
return failure(new StorageDeleteError(key, _error));
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -92,7 +92,7 @@ export class BaseStorageOperations {
|
|
|
92
92
|
await AsyncStorage.clear();
|
|
93
93
|
return success(undefined);
|
|
94
94
|
} catch (_error) {
|
|
95
|
-
return failure(new StorageDeleteError('ALL_KEYS',
|
|
95
|
+
return failure(new StorageDeleteError('ALL_KEYS', _error));
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Attachments Types
|
|
3
|
-
* Types for media attachments in flashcards and content
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type MediaAttachmentType = "image" | "audio" | "video";
|
|
7
|
-
export type MediaPosition = "front" | "back" | "both";
|
|
8
|
-
|
|
9
|
-
export interface MediaAttachment {
|
|
10
|
-
id: string;
|
|
11
|
-
type: MediaAttachmentType;
|
|
12
|
-
position: MediaPosition;
|
|
13
|
-
url: string;
|
|
14
|
-
localPath?: string;
|
|
15
|
-
filename: string;
|
|
16
|
-
fileSize: number;
|
|
17
|
-
mimeType: string;
|
|
18
|
-
duration?: number;
|
|
19
|
-
thumbnailUrl?: string;
|
|
20
|
-
caption?: string;
|
|
21
|
-
isDownloaded: boolean;
|
|
22
|
-
createdAt: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface MultimediaFlashcard {
|
|
26
|
-
id: string;
|
|
27
|
-
front: string;
|
|
28
|
-
back: string;
|
|
29
|
-
difficulty: "easy" | "medium" | "hard";
|
|
30
|
-
tags: string[];
|
|
31
|
-
createdAt?: string;
|
|
32
|
-
updatedAt?: string;
|
|
33
|
-
media: MediaAttachment[];
|
|
34
|
-
hasMedia: boolean;
|
|
35
|
-
mediaType: MediaAttachmentType[];
|
|
36
|
-
isDownloaded: boolean;
|
|
37
|
-
estimatedSize: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface MediaGenerationRequest {
|
|
41
|
-
type: "text_to_image" | "text_to_audio" | "image_search";
|
|
42
|
-
input: {
|
|
43
|
-
text?: string;
|
|
44
|
-
prompt?: string;
|
|
45
|
-
language?: string;
|
|
46
|
-
voice?: "male" | "female" | "neutral";
|
|
47
|
-
style?: "realistic" | "cartoon" | "artistic";
|
|
48
|
-
};
|
|
49
|
-
options: {
|
|
50
|
-
maxResults?: number;
|
|
51
|
-
quality?: "low" | "medium" | "high";
|
|
52
|
-
format?: "jpeg" | "png" | "mp3" | "wav";
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface MediaGenerationResult {
|
|
57
|
-
success: boolean;
|
|
58
|
-
attachments: MediaAttachment[];
|
|
59
|
-
creditsUsed: number;
|
|
60
|
-
processingTime: number;
|
|
61
|
-
error?: string;
|
|
62
|
-
requestId: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface MediaUploadProgress {
|
|
66
|
-
fileId: string;
|
|
67
|
-
progress: number;
|
|
68
|
-
status: "uploading" | "processing" | "completed" | "error";
|
|
69
|
-
error?: string;
|
|
70
|
-
url?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface MediaCompressionOptions {
|
|
74
|
-
quality: number;
|
|
75
|
-
maxWidth?: number;
|
|
76
|
-
maxHeight?: number;
|
|
77
|
-
maxFileSize?: number;
|
|
78
|
-
format?: "jpeg" | "png" | "webp";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface MediaFile {
|
|
82
|
-
name: string;
|
|
83
|
-
type: string;
|
|
84
|
-
size: number;
|
|
85
|
-
uri?: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface CreateMultimediaCardData {
|
|
89
|
-
front: string;
|
|
90
|
-
back: string;
|
|
91
|
-
difficulty?: "easy" | "medium" | "hard";
|
|
92
|
-
tags?: string[];
|
|
93
|
-
media?: MediaAttachment[];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface MediaValidation {
|
|
97
|
-
isValid: boolean;
|
|
98
|
-
errors: string[];
|
|
99
|
-
warnings: string[];
|
|
100
|
-
recommendations: string[];
|
|
101
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Generation Service
|
|
3
|
-
* Handles AI media generation (text-to-image, text-to-audio)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaGenerationRequest,
|
|
9
|
-
MediaGenerationResult,
|
|
10
|
-
MediaType,
|
|
11
|
-
MediaPosition,
|
|
12
|
-
} from "../../domain/entities/MediaAttachments";
|
|
13
|
-
|
|
14
|
-
export class MediaGenerationService {
|
|
15
|
-
/**
|
|
16
|
-
* Generate media from AI (text-to-image, text-to-audio, etc.)
|
|
17
|
-
*/
|
|
18
|
-
async generateMedia(
|
|
19
|
-
request: MediaGenerationRequest,
|
|
20
|
-
): Promise<MediaGenerationResult> {
|
|
21
|
-
try {
|
|
22
|
-
const startTime = Date.now();
|
|
23
|
-
|
|
24
|
-
// Simulate AI generation
|
|
25
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
26
|
-
|
|
27
|
-
const attachments: MediaAttachment[] = [];
|
|
28
|
-
|
|
29
|
-
switch (request.type) {
|
|
30
|
-
case "text_to_image":
|
|
31
|
-
for (let i = 0; i < (request.options.maxResults || 1); i++) {
|
|
32
|
-
attachments.push({
|
|
33
|
-
id: `ai_img_${Date.now()}_${i}`,
|
|
34
|
-
type: "image" as MediaType,
|
|
35
|
-
position: "both" as MediaPosition,
|
|
36
|
-
url: `https://picsum.photos/400/300?random=${Date.now() + i}`,
|
|
37
|
-
filename: `ai_generated_${i}.jpg`,
|
|
38
|
-
fileSize: 150000,
|
|
39
|
-
mimeType: "image/jpeg",
|
|
40
|
-
isDownloaded: false,
|
|
41
|
-
createdAt: new Date().toISOString(),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
|
|
46
|
-
case "text_to_audio":
|
|
47
|
-
attachments.push({
|
|
48
|
-
id: `ai_audio_${Date.now()}`,
|
|
49
|
-
type: "audio" as MediaType,
|
|
50
|
-
position: "back" as MediaPosition,
|
|
51
|
-
url: `https://example.com/audio_${Date.now()}.mp3`,
|
|
52
|
-
filename: `ai_generated_${Date.now()}.mp3`,
|
|
53
|
-
fileSize: 80000,
|
|
54
|
-
mimeType: "audio/mp3",
|
|
55
|
-
duration: 10,
|
|
56
|
-
isDownloaded: false,
|
|
57
|
-
createdAt: new Date().toISOString(),
|
|
58
|
-
});
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
success: true,
|
|
64
|
-
attachments,
|
|
65
|
-
creditsUsed: request.type === "text_to_image" ? 5 : 3,
|
|
66
|
-
processingTime: Date.now() - startTime,
|
|
67
|
-
requestId: `req_${Date.now()}`,
|
|
68
|
-
};
|
|
69
|
-
} catch (error) {
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
attachments: [],
|
|
73
|
-
creditsUsed: 0,
|
|
74
|
-
processingTime: 0,
|
|
75
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
76
|
-
requestId: "",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Optimizer Service
|
|
3
|
-
* Handles media optimization and deletion
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaCompressionOptions,
|
|
9
|
-
} from "../../domain/entities/MediaAttachments";
|
|
10
|
-
|
|
11
|
-
export class MediaOptimizerService {
|
|
12
|
-
/**
|
|
13
|
-
* Optimize media file
|
|
14
|
-
*/
|
|
15
|
-
async optimizeMedia(
|
|
16
|
-
attachment: MediaAttachment,
|
|
17
|
-
options: MediaCompressionOptions,
|
|
18
|
-
): Promise<MediaAttachment> {
|
|
19
|
-
return {
|
|
20
|
-
...attachment,
|
|
21
|
-
fileSize: Math.floor(attachment.fileSize * options.quality),
|
|
22
|
-
url: `${attachment.url}?optimized=true`,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Delete media attachment
|
|
28
|
-
*/
|
|
29
|
-
async deleteMedia(_attachmentId: string): Promise<void> {
|
|
30
|
-
// Mock implementation
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Domain - Media Picker Service
|
|
3
|
-
*
|
|
4
|
-
* Service for picking images/videos using expo-image-picker.
|
|
5
|
-
* Handles camera, gallery, and media library permissions.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as ImagePicker from "expo-image-picker";
|
|
9
|
-
import type {
|
|
10
|
-
MediaPickerOptions,
|
|
11
|
-
MediaPickerResult,
|
|
12
|
-
CameraOptions,
|
|
13
|
-
} from "../../domain/entities/Media";
|
|
14
|
-
import {
|
|
15
|
-
MediaType,
|
|
16
|
-
MediaValidationError,
|
|
17
|
-
MEDIA_CONSTANTS,
|
|
18
|
-
} from "../../domain/entities/Media";
|
|
19
|
-
import {
|
|
20
|
-
mapMediaType,
|
|
21
|
-
mapPickerResult,
|
|
22
|
-
} from "../utils/mediaPickerMappers";
|
|
23
|
-
import { PermissionManager } from "../utils/PermissionManager";
|
|
24
|
-
import { FileValidator } from "../../domain/utils/FileValidator";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Media picker service for selecting images/videos
|
|
28
|
-
*/
|
|
29
|
-
export class MediaPickerService {
|
|
30
|
-
static async launchCamera(
|
|
31
|
-
options?: CameraOptions
|
|
32
|
-
): Promise<MediaPickerResult> {
|
|
33
|
-
try {
|
|
34
|
-
const permission = await PermissionManager.requestCameraPermission();
|
|
35
|
-
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
36
|
-
return { canceled: true };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const result = await ImagePicker.launchCameraAsync({
|
|
40
|
-
mediaTypes: ["images"],
|
|
41
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
42
|
-
aspect: options?.aspect,
|
|
43
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
44
|
-
base64: options?.base64 ?? false,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
return mapPickerResult(result);
|
|
48
|
-
} catch {
|
|
49
|
-
return { canceled: true };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
static async launchCameraForVideo(
|
|
54
|
-
options?: CameraOptions
|
|
55
|
-
): Promise<MediaPickerResult> {
|
|
56
|
-
try {
|
|
57
|
-
const permission = await PermissionManager.requestCameraPermission();
|
|
58
|
-
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
59
|
-
return { canceled: true };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const result = await ImagePicker.launchCameraAsync({
|
|
63
|
-
mediaTypes: ["videos"],
|
|
64
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
65
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
66
|
-
videoMaxDuration: options?.videoMaxDuration,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return mapPickerResult(result);
|
|
70
|
-
} catch {
|
|
71
|
-
return { canceled: true };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
static async pickImage(
|
|
76
|
-
options?: MediaPickerOptions
|
|
77
|
-
): Promise<MediaPickerResult> {
|
|
78
|
-
try {
|
|
79
|
-
const permission = await PermissionManager.requestMediaLibraryPermission();
|
|
80
|
-
if (!PermissionManager.isPermissionGranted(permission)) {
|
|
81
|
-
return {
|
|
82
|
-
canceled: true,
|
|
83
|
-
error: MediaValidationError.PERMISSION_DENIED,
|
|
84
|
-
errorMessage: "Permission to access media library was denied",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const result = await ImagePicker.launchImageLibraryAsync({
|
|
89
|
-
mediaTypes: mapMediaType(options?.mediaTypes),
|
|
90
|
-
allowsEditing: options?.allowsEditing ?? false,
|
|
91
|
-
allowsMultipleSelection: options?.allowsMultipleSelection ?? false,
|
|
92
|
-
aspect: options?.aspect,
|
|
93
|
-
quality: options?.quality ?? MEDIA_CONSTANTS.DEFAULT_QUALITY,
|
|
94
|
-
selectionLimit:
|
|
95
|
-
options?.selectionLimit ?? MEDIA_CONSTANTS.DEFAULT_SELECTION_LIMIT,
|
|
96
|
-
base64: options?.base64 ?? false,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const mappedResult = mapPickerResult(result);
|
|
100
|
-
|
|
101
|
-
// Validate file size if not canceled and has assets
|
|
102
|
-
if (!mappedResult.canceled && mappedResult.assets && mappedResult.assets.length > 0) {
|
|
103
|
-
const validation = FileValidator.validateAssets(mappedResult.assets, {
|
|
104
|
-
maxFileSizeMB: options?.maxFileSizeMB,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
if (!validation.valid) {
|
|
108
|
-
return {
|
|
109
|
-
canceled: true,
|
|
110
|
-
error: validation.error,
|
|
111
|
-
errorMessage: validation.errorMessage,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return mappedResult;
|
|
117
|
-
} catch {
|
|
118
|
-
return { canceled: true };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
static async pickSingleImage(
|
|
123
|
-
options?: Omit<MediaPickerOptions, "allowsMultipleSelection">
|
|
124
|
-
): Promise<MediaPickerResult> {
|
|
125
|
-
return MediaPickerService.pickImage({
|
|
126
|
-
...options,
|
|
127
|
-
allowsMultipleSelection: false,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static async pickMultipleImages(
|
|
132
|
-
options?: Omit<MediaPickerOptions, "allowsMultipleSelection">
|
|
133
|
-
): Promise<MediaPickerResult> {
|
|
134
|
-
return MediaPickerService.pickImage({
|
|
135
|
-
...options,
|
|
136
|
-
allowsMultipleSelection: true,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
static async pickVideo(
|
|
141
|
-
options?: Omit<MediaPickerOptions, "mediaTypes">
|
|
142
|
-
): Promise<MediaPickerResult> {
|
|
143
|
-
return MediaPickerService.pickImage({
|
|
144
|
-
...options,
|
|
145
|
-
mediaTypes: MediaType.VIDEO,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
static async pickMedia(
|
|
150
|
-
options?: MediaPickerOptions
|
|
151
|
-
): Promise<MediaPickerResult> {
|
|
152
|
-
return MediaPickerService.pickImage({
|
|
153
|
-
...options,
|
|
154
|
-
mediaTypes: MediaType.ALL,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Save Service
|
|
3
|
-
* Saves media to device storage using expo-file-system
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as FileSystem from "expo-file-system";
|
|
7
|
-
import { MediaLibraryPermission } from "../../domain/entities/Media";
|
|
8
|
-
|
|
9
|
-
export interface SaveResult {
|
|
10
|
-
success: boolean;
|
|
11
|
-
path?: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SaveOptions {
|
|
16
|
-
album?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Service for saving media to gallery
|
|
21
|
-
*/
|
|
22
|
-
export class MediaSaveService {
|
|
23
|
-
/**
|
|
24
|
-
* Request media library write permission
|
|
25
|
-
*/
|
|
26
|
-
static async requestPermission(): Promise<MediaLibraryPermission> {
|
|
27
|
-
return MediaLibraryPermission.GRANTED;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get current permission status
|
|
32
|
-
*/
|
|
33
|
-
static async getPermissionStatus(): Promise<MediaLibraryPermission> {
|
|
34
|
-
return MediaLibraryPermission.GRANTED;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Save image to gallery
|
|
39
|
-
*/
|
|
40
|
-
static async saveImage(
|
|
41
|
-
uri: string,
|
|
42
|
-
options?: SaveOptions,
|
|
43
|
-
): Promise<SaveResult> {
|
|
44
|
-
return MediaSaveService.saveToStorage(uri, "image", options);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Save video to gallery
|
|
49
|
-
*/
|
|
50
|
-
static async saveVideo(
|
|
51
|
-
uri: string,
|
|
52
|
-
options?: SaveOptions,
|
|
53
|
-
): Promise<SaveResult> {
|
|
54
|
-
return MediaSaveService.saveToStorage(uri, "video", options);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Save media (image or video) to storage
|
|
59
|
-
*/
|
|
60
|
-
private static async saveToStorage(
|
|
61
|
-
uri: string,
|
|
62
|
-
mediaType: "image" | "video",
|
|
63
|
-
_options?: SaveOptions,
|
|
64
|
-
): Promise<SaveResult> {
|
|
65
|
-
try {
|
|
66
|
-
const timestamp = Date.now();
|
|
67
|
-
const extension = mediaType === "image" ? "jpg" : "mp4";
|
|
68
|
-
const filename = `${mediaType}_${timestamp}.${extension}`;
|
|
69
|
-
|
|
70
|
-
const directory = (FileSystem as { documentDirectory?: string | null }).documentDirectory;
|
|
71
|
-
if (!directory) {
|
|
72
|
-
return {
|
|
73
|
-
success: false,
|
|
74
|
-
error: "Document directory not available",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const destination = `${directory}${filename}`;
|
|
79
|
-
|
|
80
|
-
await FileSystem.copyAsync({
|
|
81
|
-
from: uri,
|
|
82
|
-
to: destination,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
success: true,
|
|
87
|
-
path: destination,
|
|
88
|
-
};
|
|
89
|
-
} catch (error) {
|
|
90
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
91
|
-
return {
|
|
92
|
-
success: false,
|
|
93
|
-
error: `Failed to save media: ${message}`,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Upload Service
|
|
3
|
-
* Handles media upload, download, and URL operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
MediaAttachment,
|
|
8
|
-
MediaCompressionOptions,
|
|
9
|
-
MediaFile,
|
|
10
|
-
} from "../../domain/entities/MediaAttachments";
|
|
11
|
-
import { generateThumbnail, getMediaDuration } from "../utils/file-media-utils";
|
|
12
|
-
import { getMediaTypeFromMime } from "../utils/mime-type-detector";
|
|
13
|
-
|
|
14
|
-
export class MediaUploadService {
|
|
15
|
-
/**
|
|
16
|
-
* Upload media file with optional compression
|
|
17
|
-
*/
|
|
18
|
-
async uploadMedia(
|
|
19
|
-
file: MediaFile,
|
|
20
|
-
_options?: MediaCompressionOptions,
|
|
21
|
-
): Promise<MediaAttachment> {
|
|
22
|
-
try {
|
|
23
|
-
// Simulate upload process
|
|
24
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
25
|
-
|
|
26
|
-
const attachment: MediaAttachment = {
|
|
27
|
-
id: `media_${Date.now()}`,
|
|
28
|
-
type: getMediaTypeFromMime(file.type),
|
|
29
|
-
position: "both",
|
|
30
|
-
url: `https://storage.example.com/media/${Date.now()}_${file.name}`,
|
|
31
|
-
filename: file.name,
|
|
32
|
-
fileSize: file.size || 100000,
|
|
33
|
-
mimeType: file.type,
|
|
34
|
-
duration: await getMediaDuration(file),
|
|
35
|
-
thumbnailUrl: generateThumbnail(file),
|
|
36
|
-
caption: "",
|
|
37
|
-
isDownloaded: true,
|
|
38
|
-
createdAt: new Date().toISOString(),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return attachment;
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw new Error(`Failed to upload media: ${error}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get media URL
|
|
49
|
-
*/
|
|
50
|
-
async getMediaUrl(attachmentId: string): Promise<string> {
|
|
51
|
-
return `https://storage.example.com/media/${attachmentId}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Download media to local storage
|
|
56
|
-
*/
|
|
57
|
-
async downloadMedia(attachmentId: string): Promise<string> {
|
|
58
|
-
return `/local/storage/${attachmentId}`;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Validation Service
|
|
3
|
-
* Handles media file validation before upload
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { formatFileSize } from "../utils/media-collection-utils";
|
|
7
|
-
import type { MediaValidation, MediaFile } from "../../domain/entities/MediaAttachments";
|
|
8
|
-
|
|
9
|
-
export class MediaValidationService {
|
|
10
|
-
/**
|
|
11
|
-
* Validate media file before upload
|
|
12
|
-
*/
|
|
13
|
-
async validateMedia(file: MediaFile): Promise<MediaValidation> {
|
|
14
|
-
try {
|
|
15
|
-
const errors: string[] = [];
|
|
16
|
-
const warnings: string[] = [];
|
|
17
|
-
const recommendations: string[] = [];
|
|
18
|
-
|
|
19
|
-
// File size validation
|
|
20
|
-
const maxSize = 50 * 1024 * 1024; // 50MB
|
|
21
|
-
if (file.size > maxSize) {
|
|
22
|
-
errors.push(
|
|
23
|
-
`File size (${formatFileSize(file.size)}) exceeds maximum allowed size (${formatFileSize(maxSize)})`,
|
|
24
|
-
);
|
|
25
|
-
} else if (file.size > 10 * 1024 * 1024) {
|
|
26
|
-
warnings.push(`Large file size may impact performance`);
|
|
27
|
-
recommendations.push("Consider compressing file");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// File type validation
|
|
31
|
-
const supportedTypes = [
|
|
32
|
-
"image/jpeg",
|
|
33
|
-
"image/png",
|
|
34
|
-
"image/webp",
|
|
35
|
-
"audio/mp3",
|
|
36
|
-
"audio/wav",
|
|
37
|
-
"audio/m4a",
|
|
38
|
-
"video/mp4",
|
|
39
|
-
"video/mov",
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
if (!supportedTypes.includes(file.type)) {
|
|
43
|
-
errors.push(`Unsupported file type: ${file.type}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
isValid: errors.length === 0,
|
|
48
|
-
errors,
|
|
49
|
-
warnings,
|
|
50
|
-
recommendations,
|
|
51
|
-
};
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return {
|
|
54
|
-
isValid: false,
|
|
55
|
-
errors: [error instanceof Error ? error.message : "Validation failed"],
|
|
56
|
-
warnings: [],
|
|
57
|
-
recommendations: [],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|