@umituz/react-native-ai-generation-content 1.23.4 → 1.24.1
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 +3 -2
- package/src/domain/entities/step-config.types.ts +133 -0
- package/src/domains/scenarios/configs/wizard-configs.ts +187 -0
- package/src/domains/scenarios/index.ts +8 -0
- package/src/domains/wizard/domain/entities/wizard-config.types.ts +214 -0
- package/src/domains/wizard/index.ts +62 -0
- package/src/domains/wizard/infrastructure/builders/dynamic-step-builder.ts +107 -0
- package/src/domains/wizard/infrastructure/renderers/step-renderer.tsx +106 -0
- package/src/domains/wizard/presentation/components/GenericWizardFlow.tsx +189 -0
- package/src/domains/wizard/presentation/hooks/usePhotoUploadState.ts +115 -0
- package/src/domains/wizard/presentation/screens/GenericPhotoUploadScreen.tsx +226 -0
- package/src/domains/wizard/presentation/steps/PhotoUploadStep.tsx +67 -0
- package/src/domains/wizard/presentation/steps/SelectionStep.tsx +244 -0
- package/src/domains/wizard/presentation/steps/TextInputStep.tsx +199 -0
- package/src/features/couple-future/domain/wizard-config.adapter.ts +76 -0
- package/src/features/couple-future/presentation/components/CoupleFutureWizard.tsx +45 -1
- package/src/index.ts +3 -0
- package/src/infrastructure/flow/step-builder.ts +226 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Photo Upload Screen
|
|
3
|
+
* Used by wizard domain for ANY photo upload step
|
|
4
|
+
* NO feature-specific concepts (no partner, couple, etc.)
|
|
5
|
+
* Works for: couple features, face-swap, image-to-video, ANY photo upload need
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useMemo } from "react";
|
|
9
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
10
|
+
import {
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
ScreenLayout,
|
|
13
|
+
AtomicText,
|
|
14
|
+
AtomicIcon,
|
|
15
|
+
NavigationHeader,
|
|
16
|
+
type DesignTokens,
|
|
17
|
+
} from "@umituz/react-native-design-system";
|
|
18
|
+
import { PhotoUploadCard } from "../../../../presentation/components";
|
|
19
|
+
import { FaceDetectionToggle } from "../../../../domains/face-detection";
|
|
20
|
+
import type { UploadedImage } from "../../../../features/partner-upload/domain/types";
|
|
21
|
+
import { usePhotoUploadState } from "../hooks/usePhotoUploadState";
|
|
22
|
+
|
|
23
|
+
export interface PhotoUploadScreenTranslations {
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly subtitle: string;
|
|
26
|
+
readonly continue: string;
|
|
27
|
+
readonly tapToUpload: string;
|
|
28
|
+
readonly selectPhoto: string;
|
|
29
|
+
readonly change: string;
|
|
30
|
+
readonly analyzing: string;
|
|
31
|
+
readonly fileTooLarge: string;
|
|
32
|
+
readonly maxFileSize: string;
|
|
33
|
+
readonly error: string;
|
|
34
|
+
readonly uploadFailed: string;
|
|
35
|
+
readonly aiDisclosure?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PhotoUploadScreenConfig {
|
|
39
|
+
readonly showFaceDetection?: boolean;
|
|
40
|
+
readonly showPhotoTips?: boolean;
|
|
41
|
+
readonly maxFileSizeMB?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PhotoUploadScreenProps {
|
|
45
|
+
readonly translations: PhotoUploadScreenTranslations;
|
|
46
|
+
readonly t: (key: string) => string;
|
|
47
|
+
readonly config?: PhotoUploadScreenConfig;
|
|
48
|
+
readonly faceDetectionEnabled?: boolean;
|
|
49
|
+
readonly onFaceDetectionToggle?: (enabled: boolean) => void;
|
|
50
|
+
readonly onBack: () => void;
|
|
51
|
+
readonly onContinue: (image: UploadedImage) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DEFAULT_CONFIG: PhotoUploadScreenConfig = {
|
|
55
|
+
showFaceDetection: false,
|
|
56
|
+
showPhotoTips: true,
|
|
57
|
+
maxFileSizeMB: 10,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const GenericPhotoUploadScreen: React.FC<PhotoUploadScreenProps> = ({
|
|
61
|
+
translations,
|
|
62
|
+
t,
|
|
63
|
+
config = DEFAULT_CONFIG,
|
|
64
|
+
faceDetectionEnabled = false,
|
|
65
|
+
onFaceDetectionToggle,
|
|
66
|
+
onBack,
|
|
67
|
+
onContinue,
|
|
68
|
+
}) => {
|
|
69
|
+
const tokens = useAppDesignTokens();
|
|
70
|
+
|
|
71
|
+
const { image, handlePickImage, canContinue } = usePhotoUploadState({
|
|
72
|
+
config: { maxFileSizeMB: config.maxFileSizeMB },
|
|
73
|
+
translations: {
|
|
74
|
+
fileTooLarge: translations.fileTooLarge,
|
|
75
|
+
maxFileSize: translations.maxFileSize,
|
|
76
|
+
error: translations.error,
|
|
77
|
+
uploadFailed: translations.uploadFailed,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const handleContinuePress = () => {
|
|
82
|
+
if (!canContinue || !image) return;
|
|
83
|
+
onContinue(image);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
87
|
+
const showFaceDetection = config.showFaceDetection ?? false;
|
|
88
|
+
const showPhotoTips = config.showPhotoTips ?? true;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
92
|
+
<NavigationHeader
|
|
93
|
+
title={translations.title}
|
|
94
|
+
onBackPress={onBack}
|
|
95
|
+
rightElement={
|
|
96
|
+
<TouchableOpacity
|
|
97
|
+
onPress={handleContinuePress}
|
|
98
|
+
activeOpacity={0.7}
|
|
99
|
+
disabled={!canContinue || !image}
|
|
100
|
+
style={[
|
|
101
|
+
styles.continueButton,
|
|
102
|
+
{
|
|
103
|
+
backgroundColor: canContinue && image ? tokens.colors.primary : tokens.colors.surfaceVariant,
|
|
104
|
+
opacity: canContinue && image ? 1 : 0.5,
|
|
105
|
+
},
|
|
106
|
+
]}
|
|
107
|
+
>
|
|
108
|
+
<AtomicText
|
|
109
|
+
type="bodyMedium"
|
|
110
|
+
style={[
|
|
111
|
+
styles.continueText,
|
|
112
|
+
{ color: canContinue && image ? tokens.colors.onPrimary : tokens.colors.textSecondary },
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
{translations.continue}
|
|
116
|
+
</AtomicText>
|
|
117
|
+
<AtomicIcon
|
|
118
|
+
name="arrow-forward"
|
|
119
|
+
size="sm"
|
|
120
|
+
color={canContinue && image ? "onPrimary" : "textSecondary"}
|
|
121
|
+
/>
|
|
122
|
+
</TouchableOpacity>
|
|
123
|
+
}
|
|
124
|
+
/>
|
|
125
|
+
<ScreenLayout
|
|
126
|
+
edges={["left", "right"]}
|
|
127
|
+
backgroundColor="transparent"
|
|
128
|
+
scrollable={true}
|
|
129
|
+
keyboardAvoiding={true}
|
|
130
|
+
contentContainerStyle={styles.scrollContent}
|
|
131
|
+
hideScrollIndicator={true}
|
|
132
|
+
>
|
|
133
|
+
<AtomicText style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
|
|
134
|
+
{translations.subtitle}
|
|
135
|
+
</AtomicText>
|
|
136
|
+
|
|
137
|
+
{/* Photo Tips - Inline simple version */}
|
|
138
|
+
{showPhotoTips && (
|
|
139
|
+
<View style={[styles.tipsContainer, { backgroundColor: tokens.colors.surfaceVariant + "40" }]}>
|
|
140
|
+
<AtomicText type="labelSmall" style={{ color: tokens.colors.textSecondary, textAlign: "center" }}>
|
|
141
|
+
{t("photoUpload.tips")}
|
|
142
|
+
</AtomicText>
|
|
143
|
+
</View>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{showFaceDetection && onFaceDetectionToggle && (
|
|
147
|
+
<FaceDetectionToggle
|
|
148
|
+
isEnabled={faceDetectionEnabled}
|
|
149
|
+
onToggle={onFaceDetectionToggle}
|
|
150
|
+
label={t("photoUpload.faceDetection")}
|
|
151
|
+
hidden={true}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<PhotoUploadCard
|
|
156
|
+
imageUri={image?.previewUrl || null}
|
|
157
|
+
onPress={handlePickImage}
|
|
158
|
+
isValidating={false}
|
|
159
|
+
isValid={null}
|
|
160
|
+
translations={{
|
|
161
|
+
tapToUpload: translations.tapToUpload,
|
|
162
|
+
selectPhoto: translations.selectPhoto,
|
|
163
|
+
change: translations.change,
|
|
164
|
+
analyzing: translations.analyzing,
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
{translations.aiDisclosure && (
|
|
169
|
+
<View style={styles.disclosureContainer}>
|
|
170
|
+
<AtomicText
|
|
171
|
+
type="labelSmall"
|
|
172
|
+
style={[styles.disclosureText, { color: tokens.colors.textSecondary }]}
|
|
173
|
+
>
|
|
174
|
+
{translations.aiDisclosure}
|
|
175
|
+
</AtomicText>
|
|
176
|
+
</View>
|
|
177
|
+
)}
|
|
178
|
+
</ScreenLayout>
|
|
179
|
+
</View>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const createStyles = (tokens: DesignTokens) =>
|
|
184
|
+
StyleSheet.create({
|
|
185
|
+
container: {
|
|
186
|
+
flex: 1,
|
|
187
|
+
},
|
|
188
|
+
scrollContent: {
|
|
189
|
+
paddingBottom: 40,
|
|
190
|
+
},
|
|
191
|
+
subtitle: {
|
|
192
|
+
fontSize: 16,
|
|
193
|
+
textAlign: "center",
|
|
194
|
+
marginHorizontal: 24,
|
|
195
|
+
marginBottom: 24,
|
|
196
|
+
},
|
|
197
|
+
tipsContainer: {
|
|
198
|
+
marginHorizontal: 24,
|
|
199
|
+
marginBottom: 16,
|
|
200
|
+
padding: 12,
|
|
201
|
+
borderRadius: 8,
|
|
202
|
+
},
|
|
203
|
+
continueButton: {
|
|
204
|
+
flexDirection: "row",
|
|
205
|
+
alignItems: "center",
|
|
206
|
+
paddingHorizontal: tokens.spacing.md,
|
|
207
|
+
paddingVertical: tokens.spacing.xs,
|
|
208
|
+
borderRadius: tokens.borders.radius.full,
|
|
209
|
+
},
|
|
210
|
+
continueText: {
|
|
211
|
+
fontWeight: "800",
|
|
212
|
+
marginRight: 4,
|
|
213
|
+
},
|
|
214
|
+
disclosureContainer: {
|
|
215
|
+
marginTop: 24,
|
|
216
|
+
marginHorizontal: 24,
|
|
217
|
+
padding: 16,
|
|
218
|
+
borderRadius: 12,
|
|
219
|
+
backgroundColor: tokens.colors.surfaceVariant + "40",
|
|
220
|
+
},
|
|
221
|
+
disclosureText: {
|
|
222
|
+
textAlign: "center",
|
|
223
|
+
lineHeight: 18,
|
|
224
|
+
opacity: 0.8,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Photo Upload Step
|
|
3
|
+
* Used by ALL features that need photo uploads
|
|
4
|
+
* (couple, face-swap, image-to-video, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import type { PhotoUploadStepConfig } from "../../domain/entities/wizard-config.types";
|
|
9
|
+
|
|
10
|
+
// Use wizard domain's generic photo upload screen - NO feature-specific references!
|
|
11
|
+
import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
|
|
12
|
+
import type { UploadedImage } from "../../../../features/partner-upload/domain/types";
|
|
13
|
+
|
|
14
|
+
export interface PhotoUploadStepProps {
|
|
15
|
+
readonly config: PhotoUploadStepConfig;
|
|
16
|
+
readonly onContinue: (image: UploadedImage) => void;
|
|
17
|
+
readonly onBack: () => void;
|
|
18
|
+
readonly t: (key: string) => string;
|
|
19
|
+
readonly translations?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const PhotoUploadStep: React.FC<PhotoUploadStepProps> = ({
|
|
23
|
+
config,
|
|
24
|
+
onContinue,
|
|
25
|
+
onBack,
|
|
26
|
+
t,
|
|
27
|
+
translations,
|
|
28
|
+
}) => {
|
|
29
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
|
+
console.log("[PhotoUploadStep] Rendering", {
|
|
31
|
+
stepId: config.id,
|
|
32
|
+
label: config.label,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<GenericPhotoUploadScreen
|
|
38
|
+
translations={{
|
|
39
|
+
title: config.titleKey ? t(config.titleKey) : config.label || "Upload Photo",
|
|
40
|
+
subtitle: config.subtitleKey ? t(config.subtitleKey) : t("photoUpload.subtitle"),
|
|
41
|
+
continue: t("common.continue"),
|
|
42
|
+
tapToUpload: t("photoUpload.tapToUpload"),
|
|
43
|
+
selectPhoto: t("photoUpload.selectPhoto"),
|
|
44
|
+
change: t("common.change"),
|
|
45
|
+
analyzing: t("photoUpload.analyzing"),
|
|
46
|
+
fileTooLarge: t("common.errors.file_too_large"),
|
|
47
|
+
maxFileSize: t("common.errors.max_file_size"),
|
|
48
|
+
error: t("common.error"),
|
|
49
|
+
uploadFailed: t("common.errors.upload_failed"),
|
|
50
|
+
aiDisclosure: t("photoUpload.aiDisclosure"),
|
|
51
|
+
}}
|
|
52
|
+
t={t}
|
|
53
|
+
config={{
|
|
54
|
+
showFaceDetection: config.showFaceDetection ?? false,
|
|
55
|
+
showPhotoTips: config.showPhotoTips ?? true,
|
|
56
|
+
maxFileSizeMB: config.maxFileSizeMB ?? 10,
|
|
57
|
+
}}
|
|
58
|
+
onBack={onBack}
|
|
59
|
+
onContinue={(image) => {
|
|
60
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
61
|
+
console.log("[PhotoUploadStep] Photo uploaded", { stepId: config.id });
|
|
62
|
+
}
|
|
63
|
+
onContinue(image);
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Selection Step
|
|
3
|
+
* Used by ANY feature that needs selection
|
|
4
|
+
* (style, duration, aspect ratio, quality, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback } from "react";
|
|
8
|
+
import { View, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
|
|
9
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
10
|
+
import type { SelectionStepConfig } from "../../domain/entities/wizard-config.types";
|
|
11
|
+
|
|
12
|
+
export interface SelectionStepProps {
|
|
13
|
+
readonly config: SelectionStepConfig;
|
|
14
|
+
readonly onContinue: (selected: string | string[]) => void;
|
|
15
|
+
readonly onBack: () => void;
|
|
16
|
+
readonly t: (key: string) => string;
|
|
17
|
+
readonly translations?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SelectionStep: React.FC<SelectionStepProps> = ({
|
|
21
|
+
config,
|
|
22
|
+
onContinue,
|
|
23
|
+
onBack,
|
|
24
|
+
t,
|
|
25
|
+
translations,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
const [selectedIds, setSelectedIds] = useState<string[]>(
|
|
29
|
+
config.defaultValue
|
|
30
|
+
? Array.isArray(config.defaultValue)
|
|
31
|
+
? config.defaultValue
|
|
32
|
+
: [config.defaultValue]
|
|
33
|
+
: [],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
37
|
+
console.log("[SelectionStep] Rendering", {
|
|
38
|
+
stepId: config.id,
|
|
39
|
+
selectionType: config.selectionType,
|
|
40
|
+
multiSelect: config.multiSelect,
|
|
41
|
+
selectedCount: selectedIds.length,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handleOptionPress = useCallback(
|
|
46
|
+
(optionId: string) => {
|
|
47
|
+
if (config.multiSelect) {
|
|
48
|
+
// Multi-select: toggle option
|
|
49
|
+
setSelectedIds((prev) => {
|
|
50
|
+
if (prev.includes(optionId)) {
|
|
51
|
+
return prev.filter((id) => id !== optionId);
|
|
52
|
+
}
|
|
53
|
+
return [...prev, optionId];
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
// Single-select: replace selection
|
|
57
|
+
setSelectedIds([optionId]);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[config.multiSelect],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const handleContinue = useCallback(() => {
|
|
64
|
+
if (selectedIds.length === 0) {
|
|
65
|
+
return; // Don't allow continue without selection
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
69
|
+
console.log("[SelectionStep] Continue", {
|
|
70
|
+
stepId: config.id,
|
|
71
|
+
selected: config.multiSelect ? selectedIds : selectedIds[0],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Return single value for single-select, array for multi-select
|
|
76
|
+
onContinue(config.multiSelect ? selectedIds : selectedIds[0]);
|
|
77
|
+
}, [selectedIds, config, onContinue]);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
81
|
+
{/* Header */}
|
|
82
|
+
<View style={styles.header}>
|
|
83
|
+
<TouchableOpacity onPress={onBack} style={styles.backButton}>
|
|
84
|
+
<AtomicText type="body">{t("common.back")}</AtomicText>
|
|
85
|
+
</TouchableOpacity>
|
|
86
|
+
</View>
|
|
87
|
+
|
|
88
|
+
{/* Content */}
|
|
89
|
+
<ScrollView style={styles.content}>
|
|
90
|
+
{/* Title */}
|
|
91
|
+
{config.titleKey && (
|
|
92
|
+
<AtomicText type="heading2" style={styles.title}>
|
|
93
|
+
{t(config.titleKey)}
|
|
94
|
+
</AtomicText>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* Subtitle */}
|
|
98
|
+
{config.subtitleKey && (
|
|
99
|
+
<AtomicText type="body" style={styles.subtitle}>
|
|
100
|
+
{t(config.subtitleKey)}
|
|
101
|
+
</AtomicText>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{/* Options */}
|
|
105
|
+
<View style={styles.optionsContainer}>
|
|
106
|
+
{config.options.map((option) => {
|
|
107
|
+
const isSelected = selectedIds.includes(option.id);
|
|
108
|
+
return (
|
|
109
|
+
<TouchableOpacity
|
|
110
|
+
key={option.id}
|
|
111
|
+
style={[
|
|
112
|
+
styles.optionCard,
|
|
113
|
+
{
|
|
114
|
+
backgroundColor: isSelected
|
|
115
|
+
? tokens.colors.primaryContainer
|
|
116
|
+
: tokens.colors.backgroundSecondary,
|
|
117
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
118
|
+
},
|
|
119
|
+
]}
|
|
120
|
+
onPress={() => handleOptionPress(option.id)}
|
|
121
|
+
>
|
|
122
|
+
{/* Option Icon (if provided) */}
|
|
123
|
+
{option.icon && (
|
|
124
|
+
<View style={styles.iconContainer}>
|
|
125
|
+
<AtomicText type="heading3">{option.icon}</AtomicText>
|
|
126
|
+
</View>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Option Label */}
|
|
130
|
+
<AtomicText
|
|
131
|
+
type="body"
|
|
132
|
+
style={[
|
|
133
|
+
styles.optionLabel,
|
|
134
|
+
{
|
|
135
|
+
color: isSelected ? tokens.colors.onPrimaryContainer : tokens.colors.textPrimary,
|
|
136
|
+
},
|
|
137
|
+
]}
|
|
138
|
+
>
|
|
139
|
+
{option.label}
|
|
140
|
+
</AtomicText>
|
|
141
|
+
|
|
142
|
+
{/* Selection Indicator */}
|
|
143
|
+
{isSelected && (
|
|
144
|
+
<View
|
|
145
|
+
style={[
|
|
146
|
+
styles.selectedIndicator,
|
|
147
|
+
{
|
|
148
|
+
backgroundColor: tokens.colors.primary,
|
|
149
|
+
},
|
|
150
|
+
]}
|
|
151
|
+
>
|
|
152
|
+
<AtomicText type="caption" style={{ color: tokens.colors.textOnPrimary }}>
|
|
153
|
+
✓
|
|
154
|
+
</AtomicText>
|
|
155
|
+
</View>
|
|
156
|
+
)}
|
|
157
|
+
</TouchableOpacity>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</View>
|
|
161
|
+
</ScrollView>
|
|
162
|
+
|
|
163
|
+
{/* Continue Button */}
|
|
164
|
+
<View style={styles.footer}>
|
|
165
|
+
<TouchableOpacity
|
|
166
|
+
style={[
|
|
167
|
+
styles.continueButton,
|
|
168
|
+
{
|
|
169
|
+
backgroundColor: selectedIds.length > 0 ? tokens.colors.primary : tokens.colors.disabled,
|
|
170
|
+
},
|
|
171
|
+
]}
|
|
172
|
+
onPress={handleContinue}
|
|
173
|
+
disabled={selectedIds.length === 0}
|
|
174
|
+
>
|
|
175
|
+
<AtomicText
|
|
176
|
+
type="buttonLarge"
|
|
177
|
+
style={{
|
|
178
|
+
color: selectedIds.length > 0 ? tokens.colors.textOnPrimary : tokens.colors.textDisabled,
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{t("common.continue")}
|
|
182
|
+
</AtomicText>
|
|
183
|
+
</TouchableOpacity>
|
|
184
|
+
</View>
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const styles = StyleSheet.create({
|
|
190
|
+
container: {
|
|
191
|
+
flex: 1,
|
|
192
|
+
},
|
|
193
|
+
header: {
|
|
194
|
+
padding: 16,
|
|
195
|
+
},
|
|
196
|
+
backButton: {
|
|
197
|
+
alignSelf: "flex-start",
|
|
198
|
+
},
|
|
199
|
+
content: {
|
|
200
|
+
flex: 1,
|
|
201
|
+
padding: 16,
|
|
202
|
+
},
|
|
203
|
+
title: {
|
|
204
|
+
marginBottom: 8,
|
|
205
|
+
},
|
|
206
|
+
subtitle: {
|
|
207
|
+
marginBottom: 24,
|
|
208
|
+
},
|
|
209
|
+
optionsContainer: {
|
|
210
|
+
gap: 12,
|
|
211
|
+
},
|
|
212
|
+
optionCard: {
|
|
213
|
+
padding: 16,
|
|
214
|
+
borderRadius: 12,
|
|
215
|
+
borderWidth: 2,
|
|
216
|
+
flexDirection: "row",
|
|
217
|
+
alignItems: "center",
|
|
218
|
+
gap: 12,
|
|
219
|
+
},
|
|
220
|
+
iconContainer: {
|
|
221
|
+
width: 40,
|
|
222
|
+
height: 40,
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
justifyContent: "center",
|
|
225
|
+
},
|
|
226
|
+
optionLabel: {
|
|
227
|
+
flex: 1,
|
|
228
|
+
},
|
|
229
|
+
selectedIndicator: {
|
|
230
|
+
width: 24,
|
|
231
|
+
height: 24,
|
|
232
|
+
borderRadius: 12,
|
|
233
|
+
alignItems: "center",
|
|
234
|
+
justifyContent: "center",
|
|
235
|
+
},
|
|
236
|
+
footer: {
|
|
237
|
+
padding: 16,
|
|
238
|
+
},
|
|
239
|
+
continueButton: {
|
|
240
|
+
padding: 16,
|
|
241
|
+
borderRadius: 8,
|
|
242
|
+
alignItems: "center",
|
|
243
|
+
},
|
|
244
|
+
});
|