@umituz/react-native-ai-generation-content 1.15.3 → 1.17.0
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/features/photo-restoration/domain/index.ts +1 -0
- package/src/features/photo-restoration/domain/types/index.ts +13 -0
- package/src/features/photo-restoration/domain/types/photo-restore.types.ts +57 -0
- package/src/features/photo-restoration/domain/types/provider.types.ts +23 -0
- package/src/features/photo-restoration/index.ts +39 -1
- package/src/features/photo-restoration/infrastructure/index.ts +1 -0
- package/src/features/photo-restoration/infrastructure/services/index.ts +7 -0
- package/src/features/photo-restoration/infrastructure/services/photo-restore-executor.ts +64 -0
- package/src/features/photo-restoration/infrastructure/services/photo-restore-provider-registry.ts +77 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreFeature.tsx +175 -0
- package/src/features/photo-restoration/presentation/components/PhotoRestoreResultView.tsx +98 -0
- package/src/features/photo-restoration/presentation/components/index.ts +4 -0
- package/src/features/photo-restoration/presentation/hooks/index.ts +5 -0
- package/src/features/photo-restoration/presentation/hooks/usePhotoRestoreFeature.ts +132 -0
- package/src/features/photo-restoration/presentation/index.ts +2 -0
- package/src/features/upscaling/domain/types/upscale.types.ts +3 -0
- package/src/features/upscaling/index.ts +3 -0
- package/src/features/upscaling/presentation/components/ComparisonSlider.tsx +206 -0
- package/src/features/upscaling/presentation/components/UpscaleFeature.tsx +10 -3
- package/src/features/upscaling/presentation/components/UpscaleResultView.tsx +27 -23
- package/src/features/upscaling/presentation/components/index.ts +2 -0
- package/src/index.ts +6 -0
- package/src/features/photo-restoration/domain/entities.ts +0 -48
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
PhotoRestoreOptions,
|
|
3
|
+
PhotoRestoreRequest,
|
|
4
|
+
PhotoRestoreResult,
|
|
5
|
+
PhotoRestoreFeatureState,
|
|
6
|
+
PhotoRestoreTranslations,
|
|
7
|
+
PhotoRestoreFeatureConfig,
|
|
8
|
+
} from "./photo-restore.types";
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
IPhotoRestoreProvider,
|
|
12
|
+
PhotoRestoreProviderEntry,
|
|
13
|
+
} from "./provider.types";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photo Restore Feature Types
|
|
3
|
+
* Request, Result, Config types for photo restoration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface PhotoRestoreOptions {
|
|
7
|
+
fixScratches?: boolean;
|
|
8
|
+
enhanceFaces?: boolean;
|
|
9
|
+
colorize?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PhotoRestoreRequest {
|
|
13
|
+
imageUri: string;
|
|
14
|
+
userId: string;
|
|
15
|
+
options?: PhotoRestoreOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PhotoRestoreResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
imageUrl?: string;
|
|
21
|
+
imageBase64?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
requestId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PhotoRestoreFeatureState {
|
|
27
|
+
imageUri: string | null;
|
|
28
|
+
processedUrl: string | null;
|
|
29
|
+
isProcessing: boolean;
|
|
30
|
+
progress: number;
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PhotoRestoreTranslations {
|
|
35
|
+
uploadTitle: string;
|
|
36
|
+
uploadSubtitle: string;
|
|
37
|
+
uploadChange: string;
|
|
38
|
+
uploadAnalyzing: string;
|
|
39
|
+
description: string;
|
|
40
|
+
processingText: string;
|
|
41
|
+
processButtonText: string;
|
|
42
|
+
successText: string;
|
|
43
|
+
saveButtonText: string;
|
|
44
|
+
tryAnotherText: string;
|
|
45
|
+
beforeLabel?: string;
|
|
46
|
+
afterLabel?: string;
|
|
47
|
+
compareHint?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PhotoRestoreFeatureConfig {
|
|
51
|
+
providerId?: string;
|
|
52
|
+
creditCost?: number;
|
|
53
|
+
onImageSelect?: (uri: string) => void;
|
|
54
|
+
onProcessingStart?: () => void;
|
|
55
|
+
onProcessingComplete?: (result: PhotoRestoreResult) => void;
|
|
56
|
+
onError?: (error: string) => void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photo Restore Provider Types
|
|
3
|
+
* Interface for provider implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PhotoRestoreRequest, PhotoRestoreResult } from "./photo-restore.types";
|
|
7
|
+
|
|
8
|
+
export interface IPhotoRestoreProvider {
|
|
9
|
+
readonly providerId: string;
|
|
10
|
+
readonly providerName: string;
|
|
11
|
+
|
|
12
|
+
isAvailable(): boolean;
|
|
13
|
+
|
|
14
|
+
restore(
|
|
15
|
+
request: PhotoRestoreRequest,
|
|
16
|
+
onProgress?: (progress: number) => void,
|
|
17
|
+
): Promise<PhotoRestoreResult>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PhotoRestoreProviderEntry {
|
|
21
|
+
provider: IPhotoRestoreProvider;
|
|
22
|
+
priority: number;
|
|
23
|
+
}
|
|
@@ -1 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Photo Restoration Feature
|
|
3
|
+
* Provider-agnostic photo restoration feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Domain Types
|
|
7
|
+
export type {
|
|
8
|
+
PhotoRestoreOptions,
|
|
9
|
+
PhotoRestoreRequest,
|
|
10
|
+
PhotoRestoreResult,
|
|
11
|
+
PhotoRestoreFeatureState,
|
|
12
|
+
PhotoRestoreTranslations,
|
|
13
|
+
PhotoRestoreFeatureConfig,
|
|
14
|
+
IPhotoRestoreProvider,
|
|
15
|
+
PhotoRestoreProviderEntry,
|
|
16
|
+
} from "./domain";
|
|
17
|
+
|
|
18
|
+
// Infrastructure Services
|
|
19
|
+
export {
|
|
20
|
+
photoRestoreProviderRegistry,
|
|
21
|
+
executePhotoRestore,
|
|
22
|
+
getAvailablePhotoRestoreProvider,
|
|
23
|
+
hasPhotoRestoreProvider,
|
|
24
|
+
} from "./infrastructure";
|
|
25
|
+
export type { ExecutePhotoRestoreOptions } from "./infrastructure";
|
|
26
|
+
|
|
27
|
+
// Presentation Hooks
|
|
28
|
+
export { usePhotoRestoreFeature } from "./presentation";
|
|
29
|
+
export type {
|
|
30
|
+
UsePhotoRestoreFeatureProps,
|
|
31
|
+
UsePhotoRestoreFeatureReturn,
|
|
32
|
+
} from "./presentation";
|
|
33
|
+
|
|
34
|
+
// Presentation Components
|
|
35
|
+
export { PhotoRestoreFeature, PhotoRestoreResultView } from "./presentation";
|
|
36
|
+
export type {
|
|
37
|
+
PhotoRestoreFeatureProps,
|
|
38
|
+
PhotoRestoreResultViewProps,
|
|
39
|
+
} from "./presentation";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./services";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { photoRestoreProviderRegistry } from "./photo-restore-provider-registry";
|
|
2
|
+
export {
|
|
3
|
+
executePhotoRestore,
|
|
4
|
+
getAvailablePhotoRestoreProvider,
|
|
5
|
+
hasPhotoRestoreProvider,
|
|
6
|
+
} from "./photo-restore-executor";
|
|
7
|
+
export type { ExecutePhotoRestoreOptions } from "./photo-restore-executor";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photo Restore Executor
|
|
3
|
+
* Executes photo restore operations using registered providers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { photoRestoreProviderRegistry } from "./photo-restore-provider-registry";
|
|
7
|
+
import type { PhotoRestoreRequest, PhotoRestoreResult } from "../../domain/types";
|
|
8
|
+
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
export interface ExecutePhotoRestoreOptions {
|
|
12
|
+
providerId?: string;
|
|
13
|
+
onProgress?: (progress: number) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function executePhotoRestore(
|
|
17
|
+
request: PhotoRestoreRequest,
|
|
18
|
+
options?: ExecutePhotoRestoreOptions,
|
|
19
|
+
): Promise<PhotoRestoreResult> {
|
|
20
|
+
const provider = photoRestoreProviderRegistry.get(options?.providerId);
|
|
21
|
+
|
|
22
|
+
if (!provider) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: "No photo restore provider available",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!provider.isAvailable()) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: `Provider ${provider.providerId} is not available`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log(`[PhotoRestoreExecutor] Using provider: ${provider.providerId}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
return await provider.restore(request, options?.onProgress);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
|
|
46
|
+
if (__DEV__) {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.error("[PhotoRestoreExecutor] Error:", message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: message,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getAvailablePhotoRestoreProvider(providerId?: string) {
|
|
59
|
+
return photoRestoreProviderRegistry.get(providerId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function hasPhotoRestoreProvider(providerId: string): boolean {
|
|
63
|
+
return photoRestoreProviderRegistry.hasProvider(providerId);
|
|
64
|
+
}
|
package/src/features/photo-restoration/infrastructure/services/photo-restore-provider-registry.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photo Restore Provider Registry
|
|
3
|
+
* Manages registered photo restore providers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
IPhotoRestoreProvider,
|
|
8
|
+
PhotoRestoreProviderEntry,
|
|
9
|
+
} from "../../domain/types";
|
|
10
|
+
|
|
11
|
+
declare const __DEV__: boolean;
|
|
12
|
+
|
|
13
|
+
class PhotoRestoreProviderRegistry {
|
|
14
|
+
private providers: Map<string, PhotoRestoreProviderEntry> = new Map();
|
|
15
|
+
private defaultProviderId: string | null = null;
|
|
16
|
+
|
|
17
|
+
register(provider: IPhotoRestoreProvider, priority = 0): void {
|
|
18
|
+
this.providers.set(provider.providerId, { provider, priority });
|
|
19
|
+
|
|
20
|
+
if (__DEV__) {
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log(
|
|
23
|
+
`[PhotoRestoreRegistry] Registered provider: ${provider.providerId}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
unregister(providerId: string): void {
|
|
29
|
+
this.providers.delete(providerId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setDefault(providerId: string): void {
|
|
33
|
+
if (!this.providers.has(providerId)) {
|
|
34
|
+
throw new Error(`Provider ${providerId} not registered`);
|
|
35
|
+
}
|
|
36
|
+
this.defaultProviderId = providerId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(providerId?: string): IPhotoRestoreProvider | null {
|
|
40
|
+
const id = providerId || this.defaultProviderId;
|
|
41
|
+
if (!id) return this.getHighestPriority();
|
|
42
|
+
|
|
43
|
+
const entry = this.providers.get(id);
|
|
44
|
+
return entry?.provider || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getAvailable(): IPhotoRestoreProvider | null {
|
|
48
|
+
const sorted = this.getSortedProviders();
|
|
49
|
+
return sorted.find((p) => p.isAvailable()) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hasProvider(providerId: string): boolean {
|
|
53
|
+
return this.providers.has(providerId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getProviderIds(): string[] {
|
|
57
|
+
return Array.from(this.providers.keys());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private getHighestPriority(): IPhotoRestoreProvider | null {
|
|
61
|
+
const sorted = this.getSortedProviders();
|
|
62
|
+
return sorted[0] || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private getSortedProviders(): IPhotoRestoreProvider[] {
|
|
66
|
+
return Array.from(this.providers.values())
|
|
67
|
+
.sort((a, b) => b.priority - a.priority)
|
|
68
|
+
.map((entry) => entry.provider);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
reset(): void {
|
|
72
|
+
this.providers.clear();
|
|
73
|
+
this.defaultProviderId = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const photoRestoreProviderRegistry = new PhotoRestoreProviderRegistry();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhotoRestoreFeature Component
|
|
3
|
+
* Main photo restore feature UI component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useCallback, useMemo } from "react";
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
useAppDesignTokens,
|
|
10
|
+
PhotoUploadCard,
|
|
11
|
+
AtomicText,
|
|
12
|
+
AtomicButton,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
import { PhotoRestoreResultView } from "./PhotoRestoreResultView";
|
|
15
|
+
import type {
|
|
16
|
+
PhotoRestoreTranslations,
|
|
17
|
+
PhotoRestoreFeatureConfig,
|
|
18
|
+
} from "../../domain/types";
|
|
19
|
+
|
|
20
|
+
export interface PhotoRestoreFeatureProps {
|
|
21
|
+
imageUri: string | null;
|
|
22
|
+
processedUrl: string | null;
|
|
23
|
+
isProcessing: boolean;
|
|
24
|
+
progress: number;
|
|
25
|
+
error: string | null;
|
|
26
|
+
translations: PhotoRestoreTranslations;
|
|
27
|
+
config?: PhotoRestoreFeatureConfig;
|
|
28
|
+
onSelectImage: () => void;
|
|
29
|
+
onProcess: () => void;
|
|
30
|
+
onSave: () => void;
|
|
31
|
+
onReset: () => void;
|
|
32
|
+
renderProcessingModal?: (props: {
|
|
33
|
+
visible: boolean;
|
|
34
|
+
progress: number;
|
|
35
|
+
}) => React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const PhotoRestoreFeature: React.FC<PhotoRestoreFeatureProps> = ({
|
|
39
|
+
imageUri,
|
|
40
|
+
processedUrl,
|
|
41
|
+
isProcessing,
|
|
42
|
+
progress,
|
|
43
|
+
error,
|
|
44
|
+
translations,
|
|
45
|
+
onSelectImage,
|
|
46
|
+
onProcess,
|
|
47
|
+
onSave,
|
|
48
|
+
onReset,
|
|
49
|
+
renderProcessingModal,
|
|
50
|
+
}) => {
|
|
51
|
+
const tokens = useAppDesignTokens();
|
|
52
|
+
|
|
53
|
+
const photoTranslations = useMemo(
|
|
54
|
+
() => ({
|
|
55
|
+
tapToUpload: translations.uploadTitle,
|
|
56
|
+
selectPhoto: translations.uploadSubtitle,
|
|
57
|
+
change: translations.uploadChange,
|
|
58
|
+
analyzing: translations.uploadAnalyzing,
|
|
59
|
+
}),
|
|
60
|
+
[translations],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const handleProcess = useCallback(() => {
|
|
64
|
+
onProcess();
|
|
65
|
+
}, [onProcess]);
|
|
66
|
+
|
|
67
|
+
if (processedUrl && imageUri) {
|
|
68
|
+
return (
|
|
69
|
+
<ScrollView
|
|
70
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
71
|
+
contentContainerStyle={styles.content}
|
|
72
|
+
showsVerticalScrollIndicator={false}
|
|
73
|
+
>
|
|
74
|
+
<PhotoRestoreResultView
|
|
75
|
+
originalUri={imageUri}
|
|
76
|
+
processedUri={processedUrl}
|
|
77
|
+
translations={{
|
|
78
|
+
successText: translations.successText,
|
|
79
|
+
saveButtonText: translations.saveButtonText,
|
|
80
|
+
tryAnotherText: translations.tryAnotherText,
|
|
81
|
+
beforeLabel: translations.beforeLabel,
|
|
82
|
+
afterLabel: translations.afterLabel,
|
|
83
|
+
}}
|
|
84
|
+
onSave={onSave}
|
|
85
|
+
onReset={onReset}
|
|
86
|
+
/>
|
|
87
|
+
</ScrollView>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
<ScrollView
|
|
94
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
95
|
+
contentContainerStyle={styles.content}
|
|
96
|
+
showsVerticalScrollIndicator={false}
|
|
97
|
+
>
|
|
98
|
+
<AtomicText
|
|
99
|
+
type="bodyLarge"
|
|
100
|
+
style={[styles.description, { color: tokens.colors.textSecondary }]}
|
|
101
|
+
>
|
|
102
|
+
{translations.description}
|
|
103
|
+
</AtomicText>
|
|
104
|
+
|
|
105
|
+
<PhotoUploadCard
|
|
106
|
+
imageUri={imageUri}
|
|
107
|
+
onPress={onSelectImage}
|
|
108
|
+
isValidating={isProcessing}
|
|
109
|
+
disabled={isProcessing}
|
|
110
|
+
translations={photoTranslations}
|
|
111
|
+
config={{
|
|
112
|
+
aspectRatio: 1,
|
|
113
|
+
borderRadius: 24,
|
|
114
|
+
showValidationStatus: false,
|
|
115
|
+
allowChange: true,
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
{error && (
|
|
120
|
+
<View
|
|
121
|
+
style={[
|
|
122
|
+
styles.errorContainer,
|
|
123
|
+
{ backgroundColor: `${tokens.colors.error}15` },
|
|
124
|
+
]}
|
|
125
|
+
>
|
|
126
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.error }}>
|
|
127
|
+
{error}
|
|
128
|
+
</AtomicText>
|
|
129
|
+
</View>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
<View style={styles.buttonContainer}>
|
|
133
|
+
<AtomicButton
|
|
134
|
+
title={
|
|
135
|
+
isProcessing
|
|
136
|
+
? translations.processingText
|
|
137
|
+
: translations.processButtonText
|
|
138
|
+
}
|
|
139
|
+
onPress={handleProcess}
|
|
140
|
+
disabled={!imageUri || isProcessing}
|
|
141
|
+
variant="primary"
|
|
142
|
+
size="lg"
|
|
143
|
+
/>
|
|
144
|
+
</View>
|
|
145
|
+
</ScrollView>
|
|
146
|
+
|
|
147
|
+
{renderProcessingModal?.({ visible: isProcessing, progress })}
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const styles = StyleSheet.create({
|
|
153
|
+
container: {
|
|
154
|
+
flex: 1,
|
|
155
|
+
},
|
|
156
|
+
content: {
|
|
157
|
+
paddingVertical: 16,
|
|
158
|
+
},
|
|
159
|
+
description: {
|
|
160
|
+
textAlign: "center",
|
|
161
|
+
marginHorizontal: 24,
|
|
162
|
+
marginBottom: 24,
|
|
163
|
+
lineHeight: 24,
|
|
164
|
+
},
|
|
165
|
+
errorContainer: {
|
|
166
|
+
marginHorizontal: 24,
|
|
167
|
+
marginBottom: 16,
|
|
168
|
+
padding: 16,
|
|
169
|
+
borderRadius: 12,
|
|
170
|
+
},
|
|
171
|
+
buttonContainer: {
|
|
172
|
+
marginHorizontal: 24,
|
|
173
|
+
marginTop: 8,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PhotoRestoreResultView Component
|
|
3
|
+
* Displays the restored image result with before/after comparison
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicButton,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { ComparisonSlider } from "../../../background/presentation/components/ComparisonSlider";
|
|
14
|
+
import type { PhotoRestoreTranslations } from "../../domain/types";
|
|
15
|
+
|
|
16
|
+
export interface PhotoRestoreResultViewProps {
|
|
17
|
+
originalUri: string;
|
|
18
|
+
processedUri: string;
|
|
19
|
+
translations: Pick<
|
|
20
|
+
PhotoRestoreTranslations,
|
|
21
|
+
| "successText"
|
|
22
|
+
| "saveButtonText"
|
|
23
|
+
| "tryAnotherText"
|
|
24
|
+
| "beforeLabel"
|
|
25
|
+
| "afterLabel"
|
|
26
|
+
>;
|
|
27
|
+
onSave: () => void;
|
|
28
|
+
onReset: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PhotoRestoreResultView: React.FC<PhotoRestoreResultViewProps> = ({
|
|
32
|
+
originalUri,
|
|
33
|
+
processedUri,
|
|
34
|
+
translations,
|
|
35
|
+
onSave,
|
|
36
|
+
onReset,
|
|
37
|
+
}) => {
|
|
38
|
+
const tokens = useAppDesignTokens();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View style={styles.container}>
|
|
42
|
+
<AtomicText
|
|
43
|
+
type="headlineMedium"
|
|
44
|
+
style={[styles.title, { color: tokens.colors.success }]}
|
|
45
|
+
>
|
|
46
|
+
{translations.successText}
|
|
47
|
+
</AtomicText>
|
|
48
|
+
|
|
49
|
+
<ComparisonSlider
|
|
50
|
+
originalUri={originalUri}
|
|
51
|
+
processedUri={processedUri}
|
|
52
|
+
beforeLabel={translations.beforeLabel}
|
|
53
|
+
afterLabel={translations.afterLabel}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<AtomicText
|
|
57
|
+
type="bodySmall"
|
|
58
|
+
style={[styles.hint, { color: tokens.colors.textSecondary }]}
|
|
59
|
+
>
|
|
60
|
+
Drag slider to compare
|
|
61
|
+
</AtomicText>
|
|
62
|
+
|
|
63
|
+
<View style={styles.actions}>
|
|
64
|
+
<AtomicButton
|
|
65
|
+
title={translations.saveButtonText}
|
|
66
|
+
onPress={onSave}
|
|
67
|
+
variant="primary"
|
|
68
|
+
size="lg"
|
|
69
|
+
/>
|
|
70
|
+
<AtomicButton
|
|
71
|
+
title={translations.tryAnotherText}
|
|
72
|
+
onPress={onReset}
|
|
73
|
+
variant="secondary"
|
|
74
|
+
size="lg"
|
|
75
|
+
/>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const styles = StyleSheet.create({
|
|
82
|
+
container: {
|
|
83
|
+
flex: 1,
|
|
84
|
+
paddingHorizontal: 24,
|
|
85
|
+
},
|
|
86
|
+
title: {
|
|
87
|
+
textAlign: "center",
|
|
88
|
+
marginBottom: 24,
|
|
89
|
+
},
|
|
90
|
+
hint: {
|
|
91
|
+
textAlign: "center",
|
|
92
|
+
marginTop: 12,
|
|
93
|
+
marginBottom: 24,
|
|
94
|
+
},
|
|
95
|
+
actions: {
|
|
96
|
+
gap: 12,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { PhotoRestoreFeature } from "./PhotoRestoreFeature";
|
|
2
|
+
export { PhotoRestoreResultView } from "./PhotoRestoreResultView";
|
|
3
|
+
export type { PhotoRestoreFeatureProps } from "./PhotoRestoreFeature";
|
|
4
|
+
export type { PhotoRestoreResultViewProps } from "./PhotoRestoreResultView";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePhotoRestoreFeature Hook
|
|
3
|
+
* Manages photo restore feature state and actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { executePhotoRestore } from "../../infrastructure/services";
|
|
8
|
+
import type {
|
|
9
|
+
PhotoRestoreFeatureState,
|
|
10
|
+
PhotoRestoreFeatureConfig,
|
|
11
|
+
PhotoRestoreResult,
|
|
12
|
+
} from "../../domain/types";
|
|
13
|
+
|
|
14
|
+
declare const __DEV__: boolean;
|
|
15
|
+
|
|
16
|
+
export interface UsePhotoRestoreFeatureProps {
|
|
17
|
+
config?: PhotoRestoreFeatureConfig;
|
|
18
|
+
userId: string;
|
|
19
|
+
onSelectImage: () => Promise<string | null>;
|
|
20
|
+
onSaveImage: (imageUrl: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UsePhotoRestoreFeatureReturn extends PhotoRestoreFeatureState {
|
|
24
|
+
selectImage: () => Promise<void>;
|
|
25
|
+
process: () => Promise<void>;
|
|
26
|
+
save: () => Promise<void>;
|
|
27
|
+
reset: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const initialState: PhotoRestoreFeatureState = {
|
|
31
|
+
imageUri: null,
|
|
32
|
+
processedUrl: null,
|
|
33
|
+
isProcessing: false,
|
|
34
|
+
progress: 0,
|
|
35
|
+
error: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function usePhotoRestoreFeature(
|
|
39
|
+
props: UsePhotoRestoreFeatureProps,
|
|
40
|
+
): UsePhotoRestoreFeatureReturn {
|
|
41
|
+
const { config, userId, onSelectImage, onSaveImage } = props;
|
|
42
|
+
const [state, setState] = useState<PhotoRestoreFeatureState>(initialState);
|
|
43
|
+
|
|
44
|
+
const selectImage = useCallback(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const uri = await onSelectImage();
|
|
47
|
+
if (uri) {
|
|
48
|
+
setState((prev) => ({ ...prev, imageUri: uri, error: null }));
|
|
49
|
+
config?.onImageSelect?.(uri);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
54
|
+
}
|
|
55
|
+
}, [onSelectImage, config]);
|
|
56
|
+
|
|
57
|
+
const handleProgress = useCallback((progress: number) => {
|
|
58
|
+
setState((prev) => ({ ...prev, progress }));
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const process = useCallback(async () => {
|
|
62
|
+
if (!state.imageUri) return;
|
|
63
|
+
|
|
64
|
+
setState((prev) => ({
|
|
65
|
+
...prev,
|
|
66
|
+
isProcessing: true,
|
|
67
|
+
progress: 0,
|
|
68
|
+
error: null,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
config?.onProcessingStart?.();
|
|
72
|
+
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log("[usePhotoRestoreFeature] Starting photo restore process");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result: PhotoRestoreResult = await executePhotoRestore(
|
|
79
|
+
{
|
|
80
|
+
imageUri: state.imageUri,
|
|
81
|
+
userId,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
providerId: config?.providerId,
|
|
85
|
+
onProgress: handleProgress,
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (result.success && result.imageUrl) {
|
|
90
|
+
const url = result.imageUrl;
|
|
91
|
+
setState((prev) => ({
|
|
92
|
+
...prev,
|
|
93
|
+
isProcessing: false,
|
|
94
|
+
processedUrl: url,
|
|
95
|
+
progress: 100,
|
|
96
|
+
}));
|
|
97
|
+
config?.onProcessingComplete?.(result);
|
|
98
|
+
} else {
|
|
99
|
+
const errorMessage = result.error || "Processing failed";
|
|
100
|
+
setState((prev) => ({
|
|
101
|
+
...prev,
|
|
102
|
+
isProcessing: false,
|
|
103
|
+
error: errorMessage,
|
|
104
|
+
progress: 0,
|
|
105
|
+
}));
|
|
106
|
+
config?.onError?.(errorMessage);
|
|
107
|
+
}
|
|
108
|
+
}, [state.imageUri, userId, config, handleProgress]);
|
|
109
|
+
|
|
110
|
+
const save = useCallback(async () => {
|
|
111
|
+
if (!state.processedUrl) return;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await onSaveImage(state.processedUrl);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
setState((prev) => ({ ...prev, error: message }));
|
|
118
|
+
}
|
|
119
|
+
}, [state.processedUrl, onSaveImage]);
|
|
120
|
+
|
|
121
|
+
const reset = useCallback(() => {
|
|
122
|
+
setState(initialState);
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
selectImage,
|
|
128
|
+
process,
|
|
129
|
+
save,
|
|
130
|
+
reset,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -38,3 +38,6 @@ export type {
|
|
|
38
38
|
UpscaleFeatureProps,
|
|
39
39
|
UpscaleResultViewProps,
|
|
40
40
|
} from "./presentation";
|
|
41
|
+
|
|
42
|
+
// ComparisonSlider is available from ./presentation/components if needed directly
|
|
43
|
+
// but not re-exported to avoid conflict with background feature's ComparisonSlider
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison Slider Component
|
|
3
|
+
* Before/After comparison slider for upscaled images
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { memo, useState, useRef, useMemo } from "react";
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
Image,
|
|
12
|
+
PanResponder,
|
|
13
|
+
Dimensions,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import {
|
|
16
|
+
AtomicText,
|
|
17
|
+
useAppDesignTokens,
|
|
18
|
+
} from "@umituz/react-native-design-system";
|
|
19
|
+
|
|
20
|
+
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
|
21
|
+
|
|
22
|
+
export interface ComparisonSliderProps {
|
|
23
|
+
originalUri: string;
|
|
24
|
+
processedUri: string;
|
|
25
|
+
beforeLabel?: string;
|
|
26
|
+
afterLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const ComparisonSlider: React.FC<ComparisonSliderProps> = memo(
|
|
30
|
+
function ComparisonSlider({
|
|
31
|
+
originalUri,
|
|
32
|
+
processedUri,
|
|
33
|
+
beforeLabel = "Before",
|
|
34
|
+
afterLabel = "After",
|
|
35
|
+
}) {
|
|
36
|
+
const tokens = useAppDesignTokens();
|
|
37
|
+
const [sliderPosition, setSliderPosition] = useState(50);
|
|
38
|
+
const containerWidth = useRef(SCREEN_WIDTH - 48);
|
|
39
|
+
|
|
40
|
+
const panResponder = useRef(
|
|
41
|
+
PanResponder.create({
|
|
42
|
+
onStartShouldSetPanResponder: () => true,
|
|
43
|
+
onMoveShouldSetPanResponder: () => true,
|
|
44
|
+
onPanResponderMove: (_, gestureState) => {
|
|
45
|
+
const newPosition =
|
|
46
|
+
((gestureState.moveX - 24) / containerWidth.current) * 100;
|
|
47
|
+
setSliderPosition(Math.max(5, Math.min(95, newPosition)));
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
).current;
|
|
51
|
+
|
|
52
|
+
const themedStyles = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
StyleSheet.create({
|
|
55
|
+
container: {
|
|
56
|
+
width: "100%",
|
|
57
|
+
aspectRatio: 1,
|
|
58
|
+
borderRadius: 20,
|
|
59
|
+
overflow: "hidden",
|
|
60
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
61
|
+
},
|
|
62
|
+
originalContainer: {
|
|
63
|
+
position: "absolute",
|
|
64
|
+
top: 0,
|
|
65
|
+
left: 0,
|
|
66
|
+
bottom: 0,
|
|
67
|
+
overflow: "hidden",
|
|
68
|
+
borderRightWidth: 3,
|
|
69
|
+
borderRightColor: tokens.colors.surface,
|
|
70
|
+
},
|
|
71
|
+
sliderHandle: {
|
|
72
|
+
position: "absolute",
|
|
73
|
+
top: "50%",
|
|
74
|
+
left: -20,
|
|
75
|
+
width: 40,
|
|
76
|
+
height: 40,
|
|
77
|
+
borderRadius: 20,
|
|
78
|
+
justifyContent: "center",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
marginTop: -20,
|
|
81
|
+
backgroundColor: tokens.colors.surface,
|
|
82
|
+
borderWidth: 3,
|
|
83
|
+
borderColor: tokens.colors.primary,
|
|
84
|
+
},
|
|
85
|
+
labelLeft: {
|
|
86
|
+
backgroundColor: tokens.colors.surface,
|
|
87
|
+
},
|
|
88
|
+
labelRight: {
|
|
89
|
+
backgroundColor: tokens.colors.primary,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
[tokens]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<View
|
|
97
|
+
style={themedStyles.container}
|
|
98
|
+
onLayout={(e) => {
|
|
99
|
+
containerWidth.current = e.nativeEvent.layout.width;
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<View style={styles.imageContainer}>
|
|
103
|
+
<Image
|
|
104
|
+
source={{ uri: processedUri }}
|
|
105
|
+
style={styles.image}
|
|
106
|
+
resizeMode="cover"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<View
|
|
110
|
+
style={[
|
|
111
|
+
themedStyles.originalContainer,
|
|
112
|
+
{ width: `${sliderPosition}%` },
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
<Image
|
|
116
|
+
source={{ uri: originalUri }}
|
|
117
|
+
style={[styles.image, { width: containerWidth.current }]}
|
|
118
|
+
resizeMode="cover"
|
|
119
|
+
/>
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
<View
|
|
123
|
+
style={[styles.sliderLine, { left: `${sliderPosition}%` }]}
|
|
124
|
+
{...panResponder.panHandlers}
|
|
125
|
+
>
|
|
126
|
+
<View style={themedStyles.sliderHandle}>
|
|
127
|
+
<View style={styles.handleBars}>
|
|
128
|
+
<View
|
|
129
|
+
style={[
|
|
130
|
+
styles.handleBar,
|
|
131
|
+
{ backgroundColor: tokens.colors.primary },
|
|
132
|
+
]}
|
|
133
|
+
/>
|
|
134
|
+
<View
|
|
135
|
+
style={[
|
|
136
|
+
styles.handleBar,
|
|
137
|
+
{ backgroundColor: tokens.colors.primary },
|
|
138
|
+
]}
|
|
139
|
+
/>
|
|
140
|
+
</View>
|
|
141
|
+
</View>
|
|
142
|
+
</View>
|
|
143
|
+
|
|
144
|
+
<View
|
|
145
|
+
style={[styles.label, styles.labelLeft, themedStyles.labelLeft]}
|
|
146
|
+
>
|
|
147
|
+
<AtomicText type="labelSmall" style={{ color: tokens.colors.text }}>
|
|
148
|
+
{beforeLabel}
|
|
149
|
+
</AtomicText>
|
|
150
|
+
</View>
|
|
151
|
+
|
|
152
|
+
<View
|
|
153
|
+
style={[styles.label, styles.labelRight, themedStyles.labelRight]}
|
|
154
|
+
>
|
|
155
|
+
<AtomicText
|
|
156
|
+
type="labelSmall"
|
|
157
|
+
style={{ color: tokens.colors.onPrimary }}
|
|
158
|
+
>
|
|
159
|
+
{afterLabel}
|
|
160
|
+
</AtomicText>
|
|
161
|
+
</View>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
imageContainer: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
position: "relative",
|
|
172
|
+
},
|
|
173
|
+
image: {
|
|
174
|
+
width: "100%",
|
|
175
|
+
height: "100%",
|
|
176
|
+
},
|
|
177
|
+
sliderLine: {
|
|
178
|
+
position: "absolute",
|
|
179
|
+
top: 0,
|
|
180
|
+
bottom: 0,
|
|
181
|
+
width: 3,
|
|
182
|
+
marginLeft: -1.5,
|
|
183
|
+
},
|
|
184
|
+
handleBars: {
|
|
185
|
+
flexDirection: "row",
|
|
186
|
+
gap: 4,
|
|
187
|
+
},
|
|
188
|
+
handleBar: {
|
|
189
|
+
width: 3,
|
|
190
|
+
height: 16,
|
|
191
|
+
borderRadius: 2,
|
|
192
|
+
},
|
|
193
|
+
label: {
|
|
194
|
+
position: "absolute",
|
|
195
|
+
top: 12,
|
|
196
|
+
paddingHorizontal: 12,
|
|
197
|
+
paddingVertical: 6,
|
|
198
|
+
borderRadius: 16,
|
|
199
|
+
},
|
|
200
|
+
labelLeft: {
|
|
201
|
+
left: 12,
|
|
202
|
+
},
|
|
203
|
+
labelRight: {
|
|
204
|
+
right: 12,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
@@ -64,7 +64,7 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
64
64
|
onProcess();
|
|
65
65
|
}, [onProcess]);
|
|
66
66
|
|
|
67
|
-
if (processedUrl) {
|
|
67
|
+
if (processedUrl && imageUri) {
|
|
68
68
|
return (
|
|
69
69
|
<ScrollView
|
|
70
70
|
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
@@ -72,8 +72,15 @@ export const UpscaleFeature: React.FC<UpscaleFeatureProps> = ({
|
|
|
72
72
|
showsVerticalScrollIndicator={false}
|
|
73
73
|
>
|
|
74
74
|
<UpscaleResultView
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
originalUri={imageUri}
|
|
76
|
+
processedUri={processedUrl}
|
|
77
|
+
translations={{
|
|
78
|
+
successText: translations.successText,
|
|
79
|
+
saveButtonText: translations.saveButtonText,
|
|
80
|
+
tryAnotherText: translations.tryAnotherText,
|
|
81
|
+
beforeLabel: translations.beforeLabel,
|
|
82
|
+
afterLabel: translations.afterLabel,
|
|
83
|
+
}}
|
|
77
84
|
onSave={onSave}
|
|
78
85
|
onReset={onReset}
|
|
79
86
|
/>
|
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UpscaleResultView Component
|
|
3
|
-
* Displays the upscaled image result
|
|
3
|
+
* Displays the upscaled image result with before/after comparison
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicButton,
|
|
11
11
|
useAppDesignTokens,
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { ComparisonSlider } from "./ComparisonSlider";
|
|
13
14
|
import type { UpscaleTranslations } from "../../domain/types";
|
|
14
15
|
|
|
15
16
|
export interface UpscaleResultViewProps {
|
|
16
|
-
|
|
17
|
+
originalUri: string;
|
|
18
|
+
processedUri: string;
|
|
17
19
|
translations: Pick<
|
|
18
20
|
UpscaleTranslations,
|
|
19
|
-
|
|
21
|
+
| "successText"
|
|
22
|
+
| "saveButtonText"
|
|
23
|
+
| "tryAnotherText"
|
|
24
|
+
| "beforeLabel"
|
|
25
|
+
| "afterLabel"
|
|
20
26
|
>;
|
|
21
27
|
onSave: () => void;
|
|
22
28
|
onReset: () => void;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export const UpscaleResultView: React.FC<UpscaleResultViewProps> = ({
|
|
26
|
-
|
|
32
|
+
originalUri,
|
|
33
|
+
processedUri,
|
|
27
34
|
translations,
|
|
28
35
|
onSave,
|
|
29
36
|
onReset,
|
|
@@ -39,18 +46,19 @@ export const UpscaleResultView: React.FC<UpscaleResultViewProps> = ({
|
|
|
39
46
|
{translations.successText}
|
|
40
47
|
</AtomicText>
|
|
41
48
|
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
<ComparisonSlider
|
|
50
|
+
originalUri={originalUri}
|
|
51
|
+
processedUri={processedUri}
|
|
52
|
+
beforeLabel={translations.beforeLabel}
|
|
53
|
+
afterLabel={translations.afterLabel}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<AtomicText
|
|
57
|
+
type="bodySmall"
|
|
58
|
+
style={[styles.hint, { color: tokens.colors.textSecondary }]}
|
|
47
59
|
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
style={styles.image}
|
|
51
|
-
resizeMode="contain"
|
|
52
|
-
/>
|
|
53
|
-
</View>
|
|
60
|
+
Drag slider to compare
|
|
61
|
+
</AtomicText>
|
|
54
62
|
|
|
55
63
|
<View style={styles.actions}>
|
|
56
64
|
<AtomicButton
|
|
@@ -79,15 +87,11 @@ const styles = StyleSheet.create({
|
|
|
79
87
|
textAlign: "center",
|
|
80
88
|
marginBottom: 24,
|
|
81
89
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
hint: {
|
|
91
|
+
textAlign: "center",
|
|
92
|
+
marginTop: 12,
|
|
85
93
|
marginBottom: 24,
|
|
86
94
|
},
|
|
87
|
-
image: {
|
|
88
|
-
width: "100%",
|
|
89
|
-
aspectRatio: 1,
|
|
90
|
-
},
|
|
91
95
|
actions: {
|
|
92
96
|
gap: 12,
|
|
93
97
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { UpscaleFeature } from "./UpscaleFeature";
|
|
2
2
|
export { UpscaleResultView } from "./UpscaleResultView";
|
|
3
|
+
export { ComparisonSlider } from "./ComparisonSlider";
|
|
3
4
|
export type { UpscaleFeatureProps } from "./UpscaleFeature";
|
|
4
5
|
export type { UpscaleResultViewProps } from "./UpscaleResultView";
|
|
6
|
+
export type { ComparisonSliderProps } from "./ComparisonSlider";
|
package/src/index.ts
CHANGED
|
@@ -308,3 +308,9 @@ export * from "./features/background";
|
|
|
308
308
|
|
|
309
309
|
export * from "./features/upscaling";
|
|
310
310
|
|
|
311
|
+
// =============================================================================
|
|
312
|
+
// FEATURES - Photo Restoration
|
|
313
|
+
// =============================================================================
|
|
314
|
+
|
|
315
|
+
export * from "./features/photo-restoration";
|
|
316
|
+
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Photo Restoration Domain Entities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface PhotoRestorationConfig {
|
|
6
|
-
/**
|
|
7
|
-
* Whether to fix scratches and creases
|
|
8
|
-
* @default true
|
|
9
|
-
*/
|
|
10
|
-
fixScratches?: boolean;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Whether to enhance faces in the photo
|
|
14
|
-
* @default true
|
|
15
|
-
*/
|
|
16
|
-
enhanceFaces?: boolean;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Upscale factor for the result
|
|
20
|
-
* @default 2
|
|
21
|
-
*/
|
|
22
|
-
upscaleFactor?: 1 | 2 | 4;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface PhotoRestorationRequest {
|
|
26
|
-
/**
|
|
27
|
-
* The image to restore.
|
|
28
|
-
* Can be a Base64 string or a remote URL.
|
|
29
|
-
*/
|
|
30
|
-
image: string;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Optional configuration
|
|
34
|
-
*/
|
|
35
|
-
options?: PhotoRestorationConfig;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface PhotoRestorationResult {
|
|
39
|
-
/**
|
|
40
|
-
* The restored image URL or Base64
|
|
41
|
-
*/
|
|
42
|
-
imageUrl: string;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Metadata about the generation
|
|
46
|
-
*/
|
|
47
|
-
metadata?: Record<string, unknown>;
|
|
48
|
-
}
|