@umituz/react-native-ai-generation-content 1.90.2 → 1.90.3
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/domain/interfaces/app-services-auth.interface.ts +27 -0
- package/src/domain/interfaces/app-services-composite.interface.ts +29 -0
- package/src/domain/interfaces/app-services-optional.interface.ts +42 -0
- package/src/domain/interfaces/app-services.interface.ts +0 -79
- package/src/domains/background/infrastructure/services/job-poller-index.ts +7 -0
- package/src/domains/background/infrastructure/services/job-poller-utils.ts +130 -0
- package/src/domains/background/infrastructure/utils/polling-interval.util.ts +1 -1
- package/src/domains/background/presentation/hooks/use-background-generation.ts +1 -1
- package/src/domains/content-moderation/infrastructure/services/content-moderation.service.ts +1 -1
- package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +34 -8
- package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +15 -4
- package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +34 -8
- package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +19 -8
- package/src/domains/creations/domain/types/creation-categories.constants.ts +57 -0
- package/src/domains/creations/domain/types/creation-categories.helpers.ts +67 -0
- package/src/domains/creations/domain/types/creation-categories.ts +5 -111
- package/src/domains/creations/presentation/hooks/creation-validators.ts +31 -29
- package/src/domains/creations/presentation/hooks/job-poller-index.ts +10 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.filters.ts +34 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.logger.ts +76 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.stale-handlers.ts +52 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.ts +8 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +1 -1
- package/src/domains/creations/presentation-exports.ts +2 -2
- package/src/domains/face-detection/domain/entities/FaceDetection.ts +4 -3
- package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +24 -21
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-preparation.ts +58 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-prompt.ts +69 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-resolution.ts +77 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple.ts +54 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-index.ts +8 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-scenario.ts +113 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder.ts +7 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/index.ts +20 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/types.ts +44 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-end-logger.ts +18 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-start-logger.ts +57 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-step-logger.ts +106 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/index.ts +8 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/types.ts +49 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils.ts +8 -0
- package/src/domains/generation/infrastructure/flow/flow-store-actions.ts +105 -0
- package/src/domains/generation/infrastructure/flow/flow-store-initial-state.ts +26 -0
- package/src/domains/generation/infrastructure/flow/useFlowStore.ts +4 -116
- package/src/domains/generation/presentation/useAIGeneration.hook.ts +1 -1
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation-strategy-index.ts +7 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts +2 -12
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.types.ts +11 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.utils.ts +12 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +1 -220
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-builder.ts +66 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-extraction.ts +88 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-prompt-builder.ts +75 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-style-enhancements.ts +35 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-strategy-factory.ts +42 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-executor-index.ts +11 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-executor.ts +76 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-input-builder.ts +46 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-result-types.ts +17 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-submission.ts +62 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.audio-extractor.ts +27 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.executor.ts +2 -175
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.input-builder.ts +90 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +3 -108
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.types.ts +0 -129
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.validation.ts +136 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/index.ts +39 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/types.ts +37 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts +142 -0
- package/src/domains/generation/wizard/presentation/hooks/use-video-queue-utils.ts +103 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.handlers.ts +97 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.saver.ts +54 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +22 -87
- package/src/domains/generation/wizard/presentation/hooks/usePhotoUploadState.ts +8 -177
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +1 -295
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +1 -1
- package/src/domains/generation/wizard/presentation/hooks/video-queue/index.ts +82 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationCallbacks.ts +120 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationPolling.ts +76 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationRefs.ts +65 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationStart.ts +123 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue-index.ts +9 -0
- package/src/domains/image-to-video/domain/types/image-to-video-state.types.ts +11 -4
- package/src/domains/text-to-image/domain/types/text-to-image.types.ts +44 -22
- package/src/domains/text-to-video/domain/types/request.types.ts +33 -9
- package/src/domains/text-to-video/domain/types/state.types.ts +29 -9
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.handlers.ts +44 -0
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.ts +5 -51
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.types.ts +33 -0
- package/src/infrastructure/services/generation-orchestrator.service.ts +2 -2
- package/src/infrastructure/utils/couple-input-context.ts +13 -0
- package/src/infrastructure/utils/couple-input-index.ts +8 -0
- package/src/infrastructure/utils/couple-input-refiner.ts +101 -0
- package/src/infrastructure/utils/couple-input-resolver.ts +71 -0
- package/src/infrastructure/utils/couple-input-types.ts +14 -0
- package/src/infrastructure/utils/couple-input.util.ts +3 -176
- package/src/infrastructure/utils/photo-generation/photo-preparation.util.ts +1 -1
- package/src/infrastructure/validation/base-validator.ts +1 -27
- package/src/infrastructure/validation/base-validator.types.ts +32 -0
- package/src/presentation/hooks/generation/index.ts +1 -1
- package/src/presentation/hooks/generation/orchestrator-abort-logs.ts +48 -0
- package/src/presentation/hooks/generation/orchestrator-execution-logs.ts +67 -0
- package/src/presentation/hooks/generation/orchestrator-index.ts +14 -0
- package/src/presentation/hooks/generation/orchestrator-start-logs.ts +65 -0
- package/src/presentation/hooks/generation/orchestrator-state-utils.ts +17 -0
- package/src/presentation/hooks/generation/orchestrator-types.ts +55 -0
- package/src/presentation/hooks/generation/orchestrator-utils-index.ts +29 -0
- package/src/presentation/hooks/generation/orchestrator-utils.ts +25 -0
- package/src/presentation/hooks/generation/useDualImageGeneration.ts +1 -1
- package/src/presentation/hooks/generation/useImageGeneration.ts +1 -1
- package/src/presentation/hooks/generation/useVideoGeneration.ts +1 -1
- package/src/shared/hooks/factories/generation-hook-index.ts +12 -0
- package/src/shared/hooks/factories/generation-hook-types.ts +47 -0
- package/src/shared/hooks/factories/generation-hook-utils.ts +94 -0
- package/src/shared/hooks/factories/index.ts +1 -1
- package/src/shared/index.ts +1 -1
- package/src/shared/utils/calculations/aspect-ratio-calculations.ts +30 -0
- package/src/shared/utils/calculations/base64-calculations.ts +26 -0
- package/src/shared/utils/calculations/confidence-calculations.ts +21 -0
- package/src/shared/utils/calculations/cost-calculations-index.ts +43 -0
- package/src/shared/utils/calculations/cost-calculations.ts +25 -0
- package/src/shared/utils/calculations/credit-calculations.ts +37 -0
- package/src/shared/utils/calculations/index.ts +46 -0
- package/src/shared/utils/calculations/math-utilities.ts +32 -0
- package/src/shared/utils/calculations/memory-calculations.ts +33 -0
- package/src/shared/utils/calculations/pagination-calculations.ts +38 -0
- package/src/shared/utils/calculations/percentage-calculations.ts +33 -0
- package/src/shared/utils/calculations/time-calculations.ts +99 -0
- package/src/shared/utils/credit.ts +1 -1
- package/src/shared-kernel/application/hooks/index.ts +8 -0
- package/src/shared-kernel/application/hooks/use-feature-state.ts +107 -0
- package/src/shared-kernel/application/hooks/use-generation-handler.ts +110 -0
- package/src/shared-kernel/base-types/base-callbacks.types.ts +73 -0
- package/src/shared-kernel/base-types/base-feature-state.types.ts +77 -0
- package/src/shared-kernel/base-types/base-generation.types.ts +69 -0
- package/src/shared-kernel/base-types/index.ts +30 -0
- package/src/shared-kernel/domain/base-generation-strategy.ts +146 -0
- package/src/shared-kernel/domain/index.ts +7 -0
- package/src/shared-kernel/index.ts +17 -0
- package/src/shared-kernel/infrastructure/validation/common-validators.ts +126 -0
- package/src/shared-kernel/infrastructure/validation/common-validators.types.ts +33 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.ts +52 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.types.ts +38 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.utils.ts +79 -0
- package/src/shared-kernel/infrastructure/validation/index.ts +28 -0
- package/src/domains/background/infrastructure/services/job-poller.service.ts +0 -234
- package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +0 -256
- package/src/domains/generation/infrastructure/couple-generation-builder.ts +0 -374
- package/src/presentation/hooks/generation/orchestrator.ts +0 -276
- package/src/shared/hooks/factories/createGenerationHook.ts +0 -253
- package/src/shared/utils/calculations.util.ts +0 -366
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.input-builder.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Generation Strategy - Input Builder
|
|
3
|
+
* Builds video input from wizard data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
7
|
+
import type { WizardVideoInput } from "./video-generation.types";
|
|
8
|
+
import { VIDEO_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
|
|
9
|
+
import { extractPrompt, extractDuration, extractAspectRatio, extractResolution, extractQualityMode } from "../utils";
|
|
10
|
+
import { extractPhotosAsBase64 } from "./shared/photo-extraction.utils";
|
|
11
|
+
import { extractAudioAsBase64 } from "./video-generation.audio-extractor";
|
|
12
|
+
import { validatePhotoCount } from "./video-generation.validation";
|
|
13
|
+
|
|
14
|
+
export async function buildVideoInput(
|
|
15
|
+
wizardData: Record<string, unknown>,
|
|
16
|
+
scenario: WizardScenarioData,
|
|
17
|
+
): Promise<WizardVideoInput | null> {
|
|
18
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
19
|
+
console.log("[VideoStrategy] Building input", { scenarioId: scenario.id });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Extract audio (shared by all code paths)
|
|
23
|
+
const audioUrl = await extractAudioAsBase64(wizardData);
|
|
24
|
+
|
|
25
|
+
// If a pre-generated image URL exists (e.g., two-step solo video),
|
|
26
|
+
// use it directly instead of extracting photos from wizard data
|
|
27
|
+
if (scenario.preGeneratedImageUrl) {
|
|
28
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
+
console.log("[VideoStrategy] Using pre-generated image URL", {
|
|
30
|
+
url: scenario.preGeneratedImageUrl.slice(0, 80),
|
|
31
|
+
hasAudio: !!audioUrl,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const finalPrompt = extractPrompt(wizardData, scenario.aiPrompt) || scenario.aiPrompt || "";
|
|
35
|
+
const qualityMode = extractQualityMode(wizardData);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
sourceImageBase64: scenario.preGeneratedImageUrl,
|
|
39
|
+
prompt: finalPrompt,
|
|
40
|
+
duration: extractDuration(wizardData),
|
|
41
|
+
aspectRatio: extractAspectRatio(wizardData),
|
|
42
|
+
resolution: extractResolution(wizardData),
|
|
43
|
+
audioUrl,
|
|
44
|
+
qualityMode,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const photos = await extractPhotosAsBase64(wizardData, true);
|
|
49
|
+
|
|
50
|
+
const validation = validatePhotoCount(photos.length, scenario.inputType);
|
|
51
|
+
if (!validation.isValid) {
|
|
52
|
+
throw new Error(validation.errorKey ?? "error.generation.invalidInput");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let finalPrompt = extractPrompt(wizardData, scenario.aiPrompt);
|
|
56
|
+
|
|
57
|
+
if (!finalPrompt) {
|
|
58
|
+
const defaultPrompt = VIDEO_PROCESSING_PROMPTS[scenario.id];
|
|
59
|
+
if (defaultPrompt) {
|
|
60
|
+
finalPrompt = defaultPrompt;
|
|
61
|
+
} else {
|
|
62
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
+
console.error("[VideoStrategy] No prompt found for scenario:", scenario.id);
|
|
64
|
+
console.error("[VideoStrategy] Available defaults:", Object.keys(VIDEO_PROCESSING_PROMPTS));
|
|
65
|
+
}
|
|
66
|
+
throw new Error("error.generation.promptRequired");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
71
|
+
console.log("[VideoStrategy] Using clean prompt for video generation", {
|
|
72
|
+
promptLength: finalPrompt.length,
|
|
73
|
+
photoCount: photos.length,
|
|
74
|
+
hasAudio: !!audioUrl,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const qualityMode = extractQualityMode(wizardData);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
sourceImageBase64: photos[0] || undefined,
|
|
82
|
+
targetImageBase64: photos[1] || photos[0] || undefined,
|
|
83
|
+
prompt: finalPrompt,
|
|
84
|
+
duration: extractDuration(wizardData),
|
|
85
|
+
aspectRatio: extractAspectRatio(wizardData),
|
|
86
|
+
resolution: extractResolution(wizardData),
|
|
87
|
+
audioUrl,
|
|
88
|
+
qualityMode,
|
|
89
|
+
};
|
|
90
|
+
}
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -4,116 +4,10 @@
|
|
|
4
4
|
* Uses direct provider calls for generation models (text-to-video, image-to-video)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
8
7
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { extractPhotosAsBase64 } from "./shared/photo-extraction.utils";
|
|
12
|
-
import type { WizardVideoInput, CreateVideoStrategyOptions } from "./video-generation.types";
|
|
13
|
-
import { validatePhotoCount, validateWizardVideoInput } from "./video-generation.types";
|
|
8
|
+
import type { CreateVideoStrategyOptions } from "./video-generation.types";
|
|
9
|
+
import { validateWizardVideoInput } from "./video-generation.validation";
|
|
14
10
|
import { executeVideoGeneration, submitVideoGenerationToQueue } from "./video-generation.executor";
|
|
15
|
-
import { readFileAsBase64 } from "@umituz/react-native-design-system/filesystem";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extract audio from wizardData and read as base64.
|
|
21
|
-
* Audio step stores data as { uri: "file:///..." }.
|
|
22
|
-
*/
|
|
23
|
-
async function extractAudioAsBase64(wizardData: Record<string, unknown>): Promise<string | undefined> {
|
|
24
|
-
const audioData = wizardData.background_audio as { uri?: string } | undefined;
|
|
25
|
-
if (!audioData?.uri) return undefined;
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const base64 = await readFileAsBase64(audioData.uri);
|
|
29
|
-
if (!base64) return undefined;
|
|
30
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
31
|
-
console.log("[VideoStrategy] Audio extracted as base64", { length: base64.length });
|
|
32
|
-
}
|
|
33
|
-
return base64;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.warn("[VideoStrategy] Failed to read audio file:", error);
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function buildVideoInput(
|
|
41
|
-
wizardData: Record<string, unknown>,
|
|
42
|
-
scenario: WizardScenarioData,
|
|
43
|
-
): Promise<WizardVideoInput | null> {
|
|
44
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
45
|
-
console.log("[VideoStrategy] Building input", { scenarioId: scenario.id });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Extract audio (shared by all code paths)
|
|
49
|
-
const audioUrl = await extractAudioAsBase64(wizardData);
|
|
50
|
-
|
|
51
|
-
// If a pre-generated image URL exists (e.g., two-step solo video),
|
|
52
|
-
// use it directly instead of extracting photos from wizard data
|
|
53
|
-
if (scenario.preGeneratedImageUrl) {
|
|
54
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
|
-
console.log("[VideoStrategy] Using pre-generated image URL", {
|
|
56
|
-
url: scenario.preGeneratedImageUrl.slice(0, 80),
|
|
57
|
-
hasAudio: !!audioUrl,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const finalPrompt = extractPrompt(wizardData, scenario.aiPrompt) || scenario.aiPrompt || "";
|
|
61
|
-
const qualityMode = extractQualityMode(wizardData);
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
sourceImageBase64: scenario.preGeneratedImageUrl,
|
|
65
|
-
prompt: finalPrompt,
|
|
66
|
-
duration: extractDuration(wizardData),
|
|
67
|
-
aspectRatio: extractAspectRatio(wizardData),
|
|
68
|
-
resolution: extractResolution(wizardData),
|
|
69
|
-
audioUrl,
|
|
70
|
-
qualityMode,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const photos = await extractPhotosAsBase64(wizardData, true);
|
|
75
|
-
|
|
76
|
-
const validation = validatePhotoCount(photos.length, scenario.inputType);
|
|
77
|
-
if (!validation.isValid) {
|
|
78
|
-
throw new Error(validation.errorKey ?? "error.generation.invalidInput");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let finalPrompt = extractPrompt(wizardData, scenario.aiPrompt);
|
|
82
|
-
|
|
83
|
-
if (!finalPrompt) {
|
|
84
|
-
const defaultPrompt = VIDEO_PROCESSING_PROMPTS[scenario.id];
|
|
85
|
-
if (defaultPrompt) {
|
|
86
|
-
finalPrompt = defaultPrompt;
|
|
87
|
-
} else {
|
|
88
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
89
|
-
console.error("[VideoStrategy] No prompt found for scenario:", scenario.id);
|
|
90
|
-
console.error("[VideoStrategy] Available defaults:", Object.keys(VIDEO_PROCESSING_PROMPTS));
|
|
91
|
-
}
|
|
92
|
-
throw new Error("error.generation.promptRequired");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
-
console.log("[VideoStrategy] Using clean prompt for video generation", {
|
|
98
|
-
promptLength: finalPrompt.length,
|
|
99
|
-
photoCount: photos.length,
|
|
100
|
-
hasAudio: !!audioUrl,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const qualityMode = extractQualityMode(wizardData);
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
sourceImageBase64: photos[0] || undefined,
|
|
108
|
-
targetImageBase64: photos[1] || photos[0] || undefined,
|
|
109
|
-
prompt: finalPrompt,
|
|
110
|
-
duration: extractDuration(wizardData),
|
|
111
|
-
aspectRatio: extractAspectRatio(wizardData),
|
|
112
|
-
resolution: extractResolution(wizardData),
|
|
113
|
-
audioUrl,
|
|
114
|
-
qualityMode,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
11
|
|
|
118
12
|
export function createVideoStrategy(options: CreateVideoStrategyOptions): WizardStrategy {
|
|
119
13
|
const { scenario, modelConfig } = options;
|
|
@@ -153,3 +47,4 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
153
47
|
},
|
|
154
48
|
};
|
|
155
49
|
}
|
|
50
|
+
|
|
@@ -33,132 +33,3 @@ export interface CreateVideoStrategyOptions {
|
|
|
33
33
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
34
34
|
readonly creditCost: number;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
interface PhotoValidationResult {
|
|
38
|
-
isValid: boolean;
|
|
39
|
-
errorKey?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function validatePhotoCount(
|
|
43
|
-
photoCount: number,
|
|
44
|
-
inputType: ScenarioInputType | undefined,
|
|
45
|
-
): PhotoValidationResult {
|
|
46
|
-
const effectiveInputType = inputType ?? "single";
|
|
47
|
-
|
|
48
|
-
switch (effectiveInputType) {
|
|
49
|
-
case "dual":
|
|
50
|
-
if (photoCount < 2) {
|
|
51
|
-
return { isValid: false, errorKey: "error.generation.dualPhotosRequired" };
|
|
52
|
-
}
|
|
53
|
-
break;
|
|
54
|
-
case "single":
|
|
55
|
-
if (photoCount < 1) {
|
|
56
|
-
return { isValid: false, errorKey: "error.generation.photoRequired" };
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
case "text":
|
|
60
|
-
break;
|
|
61
|
-
default: {
|
|
62
|
-
// Exhaustive check: log unexpected input types in development
|
|
63
|
-
const _exhaustive: never = effectiveInputType;
|
|
64
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
65
|
-
console.warn(`[validatePhotoCount] Unknown inputType: ${_exhaustive}`);
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { isValid: true };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Type guard for WizardVideoInput
|
|
76
|
-
* Validates runtime input and provides type safety
|
|
77
|
-
*/
|
|
78
|
-
function isWizardVideoInput(input: unknown): input is WizardVideoInput {
|
|
79
|
-
if (!input || typeof input !== "object") {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const obj = input as Record<string, unknown>;
|
|
84
|
-
|
|
85
|
-
// prompt is required
|
|
86
|
-
if (typeof obj.prompt !== "string" || obj.prompt.length === 0) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Optional fields validation
|
|
91
|
-
if (obj.sourceImageBase64 !== undefined && typeof obj.sourceImageBase64 !== "string") {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (obj.targetImageBase64 !== undefined && typeof obj.targetImageBase64 !== "string") {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (obj.duration !== undefined && typeof obj.duration !== "number") {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (obj.aspectRatio !== undefined && typeof obj.aspectRatio !== "string") {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (obj.resolution !== undefined && typeof obj.resolution !== "string") {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (obj.audioUrl !== undefined && typeof obj.audioUrl !== "string") {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Validates and casts input to WizardVideoInput
|
|
120
|
-
* Throws descriptive error if validation fails
|
|
121
|
-
*/
|
|
122
|
-
export function validateWizardVideoInput(input: unknown): WizardVideoInput {
|
|
123
|
-
if (!isWizardVideoInput(input)) {
|
|
124
|
-
const errors: string[] = [];
|
|
125
|
-
|
|
126
|
-
if (!input || typeof input !== "object") {
|
|
127
|
-
throw new Error("Invalid input: expected object");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const obj = input as Record<string, unknown>;
|
|
131
|
-
|
|
132
|
-
if (typeof obj.prompt !== "string" || obj.prompt.length === 0) {
|
|
133
|
-
errors.push("prompt (string, required)");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (obj.sourceImageBase64 !== undefined && typeof obj.sourceImageBase64 !== "string") {
|
|
137
|
-
errors.push("sourceImageBase64 (string, optional)");
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (obj.targetImageBase64 !== undefined && typeof obj.targetImageBase64 !== "string") {
|
|
141
|
-
errors.push("targetImageBase64 (string, optional)");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (obj.duration !== undefined && typeof obj.duration !== "number") {
|
|
145
|
-
errors.push("duration (number, optional)");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (obj.aspectRatio !== undefined && typeof obj.aspectRatio !== "string") {
|
|
149
|
-
errors.push("aspectRatio (string, optional)");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (obj.resolution !== undefined && typeof obj.resolution !== "string") {
|
|
153
|
-
errors.push("resolution (string, optional)");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (obj.audioUrl !== undefined && typeof obj.audioUrl !== "string") {
|
|
157
|
-
errors.push("audioUrl (string, optional)");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
throw new Error(`Invalid WizardVideoInput: ${errors.join(", ")}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return input;
|
|
164
|
-
}
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.validation.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Video Generation Validation
|
|
3
|
+
* Validation functions for video generation input
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WizardVideoInput } from "./video-generation.types";
|
|
7
|
+
import type { ScenarioInputType } from "../../../../scenarios/domain/Scenario";
|
|
8
|
+
|
|
9
|
+
interface PhotoValidationResult {
|
|
10
|
+
isValid: boolean;
|
|
11
|
+
errorKey?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validatePhotoCount(
|
|
15
|
+
photoCount: number,
|
|
16
|
+
inputType: ScenarioInputType | undefined,
|
|
17
|
+
): PhotoValidationResult {
|
|
18
|
+
const effectiveInputType = inputType ?? "single";
|
|
19
|
+
|
|
20
|
+
switch (effectiveInputType) {
|
|
21
|
+
case "dual":
|
|
22
|
+
if (photoCount < 2) {
|
|
23
|
+
return { isValid: false, errorKey: "error.generation.dualPhotosRequired" };
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
case "single":
|
|
27
|
+
if (photoCount < 1) {
|
|
28
|
+
return { isValid: false, errorKey: "error.generation.photoRequired" };
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case "text":
|
|
32
|
+
break;
|
|
33
|
+
default: {
|
|
34
|
+
// Exhaustive check: log unexpected input types in development
|
|
35
|
+
const _exhaustive: never = effectiveInputType;
|
|
36
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
37
|
+
console.warn(`[validatePhotoCount] Unknown inputType: ${_exhaustive}`);
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { isValid: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Type guard for WizardVideoInput
|
|
48
|
+
* Validates runtime input and provides type safety
|
|
49
|
+
*/
|
|
50
|
+
function isWizardVideoInput(input: unknown): input is WizardVideoInput {
|
|
51
|
+
if (!input || typeof input !== "object") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const obj = input as Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
// prompt is required
|
|
58
|
+
if (typeof obj.prompt !== "string" || obj.prompt.length === 0) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Optional fields validation
|
|
63
|
+
if (obj.sourceImageBase64 !== undefined && typeof obj.sourceImageBase64 !== "string") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (obj.targetImageBase64 !== undefined && typeof obj.targetImageBase64 !== "string") {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (obj.duration !== undefined && typeof obj.duration !== "number") {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (obj.aspectRatio !== undefined && typeof obj.aspectRatio !== "string") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (obj.resolution !== undefined && typeof obj.resolution !== "string") {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (obj.audioUrl !== undefined && typeof obj.audioUrl !== "string") {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validates and casts input to WizardVideoInput
|
|
92
|
+
* Throws descriptive error if validation fails
|
|
93
|
+
*/
|
|
94
|
+
export function validateWizardVideoInput(input: unknown): WizardVideoInput {
|
|
95
|
+
if (!isWizardVideoInput(input)) {
|
|
96
|
+
const errors: string[] = [];
|
|
97
|
+
|
|
98
|
+
if (!input || typeof input !== "object") {
|
|
99
|
+
throw new Error("Invalid input: expected object");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const obj = input as Record<string, unknown>;
|
|
103
|
+
|
|
104
|
+
if (typeof obj.prompt !== "string" || obj.prompt.length === 0) {
|
|
105
|
+
errors.push("prompt (string, required)");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (obj.sourceImageBase64 !== undefined && typeof obj.sourceImageBase64 !== "string") {
|
|
109
|
+
errors.push("sourceImageBase64 (string, optional)");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (obj.targetImageBase64 !== undefined && typeof obj.targetImageBase64 !== "string") {
|
|
113
|
+
errors.push("targetImageBase64 (string, optional)");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (obj.duration !== undefined && typeof obj.duration !== "number") {
|
|
117
|
+
errors.push("duration (number, optional)");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (obj.aspectRatio !== undefined && typeof obj.aspectRatio !== "string") {
|
|
121
|
+
errors.push("aspectRatio (string, optional)");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (obj.resolution !== undefined && typeof obj.resolution !== "string") {
|
|
125
|
+
errors.push("resolution (string, optional)");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (obj.audioUrl !== undefined && typeof obj.audioUrl !== "string") {
|
|
129
|
+
errors.push("audioUrl (string, optional)");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error(`Invalid WizardVideoInput: ${errors.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Photo Upload State Hook
|
|
3
|
+
* Manages photo upload state for wizard steps
|
|
4
|
+
* Uses design system's useMedia hook for media picking with built-in validation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import type { UsePhotoUploadStateProps, UsePhotoUploadStateReturn } from "./types";
|
|
9
|
+
import { usePhotoUploadStateLogic } from "./usePhotoUploadStateLogic";
|
|
10
|
+
|
|
11
|
+
export const usePhotoUploadState = ({
|
|
12
|
+
config,
|
|
13
|
+
translations,
|
|
14
|
+
initialImage,
|
|
15
|
+
stepId,
|
|
16
|
+
onError,
|
|
17
|
+
}: UsePhotoUploadStateProps): UsePhotoUploadStateReturn => {
|
|
18
|
+
const [image, setImage] = useState<UploadedImage | null>(initialImage || null);
|
|
19
|
+
|
|
20
|
+
const { handlePickImage, clearImage, isLoading } = usePhotoUploadStateLogic(
|
|
21
|
+
config,
|
|
22
|
+
translations,
|
|
23
|
+
initialImage,
|
|
24
|
+
stepId,
|
|
25
|
+
onError,
|
|
26
|
+
setImage,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const canContinue = image !== null && !isLoading;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
image,
|
|
33
|
+
handlePickImage,
|
|
34
|
+
canContinue,
|
|
35
|
+
clearImage,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type { PhotoUploadConfig, PhotoUploadTranslations, PhotoUploadError, UsePhotoUploadStateProps, UsePhotoUploadStateReturn } from "./types";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Photo Upload State Hook - Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { UploadedImage } from "../../../../presentation/hooks/generation/useAIGenerateState";
|
|
6
|
+
|
|
7
|
+
export interface PhotoUploadConfig {
|
|
8
|
+
readonly maxFileSizeMB?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PhotoUploadTranslations {
|
|
12
|
+
readonly fileTooLarge: string;
|
|
13
|
+
readonly maxFileSize: string;
|
|
14
|
+
readonly error: string;
|
|
15
|
+
readonly uploadFailed: string;
|
|
16
|
+
readonly permissionDenied?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PhotoUploadError {
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UsePhotoUploadStateProps {
|
|
25
|
+
readonly config?: PhotoUploadConfig;
|
|
26
|
+
readonly translations: PhotoUploadTranslations;
|
|
27
|
+
readonly initialImage?: UploadedImage;
|
|
28
|
+
readonly stepId?: string;
|
|
29
|
+
readonly onError?: (error: PhotoUploadError) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UsePhotoUploadStateReturn {
|
|
33
|
+
readonly image: UploadedImage | null;
|
|
34
|
+
readonly handlePickImage: () => Promise<void>;
|
|
35
|
+
readonly canContinue: boolean;
|
|
36
|
+
readonly clearImage: () => void;
|
|
37
|
+
}
|
package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Photo Upload State Hook - Core Logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback, useRef, useEffect } from "react";
|
|
6
|
+
import { useMedia, MediaQuality, MediaValidationError, MEDIA_CONSTANTS } from "@umituz/react-native-design-system/media";
|
|
7
|
+
import type { UploadedImage } from "../../../../presentation/hooks/generation/useAIGenerateState";
|
|
8
|
+
import type { PhotoUploadConfig, PhotoUploadError, PhotoUploadTranslations } from "./types";
|
|
9
|
+
|
|
10
|
+
export function usePhotoUploadStateLogic(
|
|
11
|
+
config: PhotoUploadConfig | undefined,
|
|
12
|
+
translations: PhotoUploadTranslations,
|
|
13
|
+
initialImage: UploadedImage | undefined,
|
|
14
|
+
stepId: string | undefined,
|
|
15
|
+
onError: ((error: PhotoUploadError) => void) | undefined,
|
|
16
|
+
setImage: (image: UploadedImage | null) => void,
|
|
17
|
+
) {
|
|
18
|
+
const { pickImage, isLoading } = useMedia();
|
|
19
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
20
|
+
|
|
21
|
+
// Use refs to avoid effect re-runs on callback changes
|
|
22
|
+
const onErrorRef = useRef(onError);
|
|
23
|
+
const translationsRef = useRef(translations);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
onErrorRef.current = onError;
|
|
27
|
+
translationsRef.current = translations;
|
|
28
|
+
}, [onError, translations]);
|
|
29
|
+
|
|
30
|
+
const maxFileSizeMB = config?.maxFileSizeMB ?? MEDIA_CONSTANTS.MAX_IMAGE_SIZE_MB;
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
34
|
+
console.log("[usePhotoUploadState] Step changed, resetting image", { stepId, hasInitialImage: !!initialImage });
|
|
35
|
+
}
|
|
36
|
+
setImage(initialImage || null);
|
|
37
|
+
}, [stepId, initialImage, setImage]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Clear any existing timeout first
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
timeoutRef.current = undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isLoading) {
|
|
47
|
+
timeoutRef.current = setTimeout(() => {
|
|
48
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
49
|
+
console.warn("[usePhotoUploadState] Image picker timeout - possible stuck state (DEV warning only)");
|
|
50
|
+
}
|
|
51
|
+
// NOTE: Do NOT call onError here — the picker may still complete successfully.
|
|
52
|
+
// Showing a modal alert while the picker is open blocks the UI.
|
|
53
|
+
}, 30000);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
if (timeoutRef.current) {
|
|
58
|
+
clearTimeout(timeoutRef.current);
|
|
59
|
+
timeoutRef.current = undefined;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}, [isLoading]);
|
|
63
|
+
|
|
64
|
+
const clearImage = useCallback(() => {
|
|
65
|
+
setImage(null);
|
|
66
|
+
}, [setImage]);
|
|
67
|
+
|
|
68
|
+
const handlePickImage = useCallback(async () => {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
71
|
+
console.log("[usePhotoUploadState] Starting image pick");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = await pickImage({
|
|
75
|
+
allowsEditing: true,
|
|
76
|
+
aspect: [1, 1],
|
|
77
|
+
quality: MediaQuality.MEDIUM,
|
|
78
|
+
maxFileSizeMB,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (result.error) {
|
|
82
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
83
|
+
console.log("[usePhotoUploadState] Validation error", result.error);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (result.error === MediaValidationError.FILE_TOO_LARGE) {
|
|
87
|
+
onErrorRef.current?.({
|
|
88
|
+
title: translationsRef.current.fileTooLarge,
|
|
89
|
+
message: translationsRef.current.maxFileSize.replace("{size}", maxFileSizeMB.toString()),
|
|
90
|
+
});
|
|
91
|
+
} else if (result.error === MediaValidationError.PERMISSION_DENIED) {
|
|
92
|
+
onErrorRef.current?.({
|
|
93
|
+
title: translationsRef.current.error,
|
|
94
|
+
message: translationsRef.current.permissionDenied ?? "Permission to access media library is required",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (result.canceled || !result.assets || result.assets.length === 0) {
|
|
101
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
+
console.log("[usePhotoUploadState] Image pick canceled");
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const selectedAsset = result.assets[0];
|
|
108
|
+
if (!selectedAsset) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const uploadedImage: UploadedImage = {
|
|
113
|
+
uri: selectedAsset.uri,
|
|
114
|
+
previewUrl: selectedAsset.uri,
|
|
115
|
+
width: selectedAsset.width,
|
|
116
|
+
height: selectedAsset.height,
|
|
117
|
+
fileSize: selectedAsset.fileSize,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
setImage(uploadedImage);
|
|
121
|
+
|
|
122
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
123
|
+
const fileSizeMB = (selectedAsset.fileSize ?? 0) / (1024 * 1024);
|
|
124
|
+
console.log("[usePhotoUploadState] Image selected", {
|
|
125
|
+
width: uploadedImage.width,
|
|
126
|
+
height: uploadedImage.height,
|
|
127
|
+
fileSizeMB: fileSizeMB.toFixed(2),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
|
+
console.error("[usePhotoUploadState] Error picking image", error);
|
|
133
|
+
}
|
|
134
|
+
onErrorRef.current?.({
|
|
135
|
+
title: translationsRef.current.error,
|
|
136
|
+
message: translationsRef.current.uploadFailed,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}, [pickImage, maxFileSizeMB, setImage]);
|
|
140
|
+
|
|
141
|
+
return { handlePickImage, clearImage, isLoading };
|
|
142
|
+
}
|