@umituz/react-native-ai-generation-content 1.17.256 → 1.17.258
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 +2 -2
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +6 -6
- package/src/domains/creations/presentation/components/CreationCard.types.ts +2 -0
- package/src/domains/creations/presentation/components/index.ts +0 -1
- package/src/domains/creations/presentation/components/useCreationCardActions.ts +2 -1
- package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +3 -21
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +2 -9
- package/src/domains/result-preview/presentation/hooks/useResultActions.ts +27 -7
- package/src/features/love-message/presentation/components/CategoryGrid.tsx +2 -1
- package/src/features/love-message/presentation/components/GeneratorHeader.tsx +0 -2
- package/src/features/love-message/presentation/components/MessageListItem.tsx +2 -1
- package/src/features/love-message/presentation/components/ToneSelector.tsx +2 -1
- package/src/features/love-message/presentation/components/TypeSelector.tsx +2 -1
- package/src/features/love-message/presentation/hooks/useLoveMessageGenerator.ts +2 -2
- package/src/features/love-message/presentation/hooks/usePartnerProfile.ts +0 -1
- package/src/features/love-message/presentation/navigation/LoveMessageStack.tsx +10 -18
- package/src/features/love-message/presentation/screens/LoveMessageExploreScreen.tsx +20 -43
- package/src/features/love-message/presentation/screens/LoveMessageGeneratorScreen.tsx +43 -25
- package/src/features/love-message/presentation/screens/MessageListScreen.tsx +53 -26
- package/src/features/love-message/presentation/screens/PartnerProfileScreen.tsx +39 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.258",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@types/react": "~19.1.10",
|
|
63
63
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
64
64
|
"@typescript-eslint/parser": "^8.0.0",
|
|
65
|
-
"@umituz/react-native-design-system": "^2.
|
|
65
|
+
"@umituz/react-native-design-system": "^2.8.5",
|
|
66
66
|
"@umituz/react-native-filesystem": "latest",
|
|
67
67
|
"@umituz/react-native-firebase": "latest",
|
|
68
68
|
"@umituz/react-native-haptics": "latest",
|
|
@@ -84,14 +84,14 @@ export class CreationsWriter {
|
|
|
84
84
|
if (updates.output !== undefined) {
|
|
85
85
|
updateData.output = updates.output;
|
|
86
86
|
}
|
|
87
|
-
if (
|
|
88
|
-
updateData.rating =
|
|
87
|
+
if (updates.rating !== undefined) {
|
|
88
|
+
updateData.rating = updates.rating;
|
|
89
89
|
}
|
|
90
|
-
if (
|
|
91
|
-
updateData.ratedAt =
|
|
90
|
+
if (updates.ratedAt !== undefined) {
|
|
91
|
+
updateData.ratedAt = updates.ratedAt;
|
|
92
92
|
}
|
|
93
|
-
if (
|
|
94
|
-
updateData.isFavorite =
|
|
93
|
+
if (updates.isFavorite !== undefined) {
|
|
94
|
+
updateData.isFavorite = updates.isFavorite;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
await updateDoc(docRef, updateData);
|
|
@@ -30,7 +30,6 @@ export { GalleryHeader } from "./GalleryHeader";
|
|
|
30
30
|
export { EmptyState } from "./EmptyState";
|
|
31
31
|
export { GalleryEmptyStates } from "./GalleryEmptyStates";
|
|
32
32
|
export { CreationsHomeCard } from "./CreationsHomeCard";
|
|
33
|
-
export { CreationImageViewer } from "./CreationImageViewer";
|
|
34
33
|
export { CreationsGrid } from "./CreationsGrid";
|
|
35
34
|
|
|
36
35
|
// Detail Components
|
|
@@ -45,7 +45,8 @@ export function useCreationCardActions({
|
|
|
45
45
|
if (callbacks.onFavorite) {
|
|
46
46
|
result.push({
|
|
47
47
|
id: "favorite",
|
|
48
|
-
icon: "heart-outline",
|
|
48
|
+
icon: creation.isFavorite ? "heart" : "heart-outline",
|
|
49
|
+
color: creation.isFavorite ? "error" : undefined,
|
|
49
50
|
onPress: () => callbacks.onFavorite?.(creation),
|
|
50
51
|
});
|
|
51
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useMemo
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { View, ScrollView, StyleSheet } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
-
import { useAppDesignTokens
|
|
4
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
5
5
|
import type { Creation } from '../../domain/entities/Creation';
|
|
6
6
|
import type { CreationsConfig } from '../../domain/value-objects/CreationsConfig';
|
|
7
7
|
import { hasVideoContent, getPreviewUrl } from '../../domain/utils';
|
|
@@ -44,15 +44,6 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
44
44
|
}) => {
|
|
45
45
|
const tokens = useAppDesignTokens();
|
|
46
46
|
const insets = useSafeAreaInsets();
|
|
47
|
-
const [showFullScreen, setShowFullScreen] = useState(false);
|
|
48
|
-
|
|
49
|
-
const handleImagePress = useCallback(() => {
|
|
50
|
-
setShowFullScreen(true);
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
const handleDismissFullScreen = useCallback(() => {
|
|
54
|
-
setShowFullScreen(false);
|
|
55
|
-
}, []);
|
|
56
47
|
|
|
57
48
|
// Extract data safely
|
|
58
49
|
const metadata = (creation.metadata || {}) as CreationMetadata;
|
|
@@ -89,7 +80,7 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
89
80
|
{isVideo ? (
|
|
90
81
|
<DetailVideo videoUrl={videoUrl} _thumbnailUrl={thumbnailUrl} />
|
|
91
82
|
) : (
|
|
92
|
-
<DetailImage uri={creation.uri}
|
|
83
|
+
<DetailImage uri={creation.uri} />
|
|
93
84
|
)}
|
|
94
85
|
|
|
95
86
|
<DetailInfo title={title} date={date} />
|
|
@@ -107,15 +98,6 @@ export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
|
|
|
107
98
|
viewResultLabel={onViewResult ? t("result.viewResult") : undefined}
|
|
108
99
|
/>
|
|
109
100
|
</ScrollView>
|
|
110
|
-
|
|
111
|
-
{!isVideo && (
|
|
112
|
-
<ImageGallery
|
|
113
|
-
images={[{ uri: creation.uri }]}
|
|
114
|
-
visible={showFullScreen}
|
|
115
|
-
onDismiss={handleDismissFullScreen}
|
|
116
|
-
index={0}
|
|
117
|
-
/>
|
|
118
|
-
)}
|
|
119
101
|
</View>
|
|
120
102
|
);
|
|
121
103
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useCallback
|
|
1
|
+
import React, { useState, useMemo, useCallback } from "react";
|
|
2
2
|
import { View, FlatList, RefreshControl, StyleSheet } from "react-native";
|
|
3
3
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
4
|
import {
|
|
@@ -13,7 +13,7 @@ import { useFocusEffect } from "@react-navigation/native";
|
|
|
13
13
|
import { useCreations } from "../hooks/useCreations";
|
|
14
14
|
import { useDeleteCreation } from "../hooks/useDeleteCreation";
|
|
15
15
|
import { useGalleryFilters } from "../hooks/useGalleryFilters";
|
|
16
|
-
import { GalleryHeader, CreationCard,
|
|
16
|
+
import { GalleryHeader, CreationCard, GalleryEmptyStates } from "../components";
|
|
17
17
|
import { MEDIA_FILTER_OPTIONS, STATUS_FILTER_OPTIONS } from "../../domain/types/creation-filter";
|
|
18
18
|
import type { Creation } from "../../domain/entities/Creation";
|
|
19
19
|
import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
|
|
@@ -26,8 +26,6 @@ interface CreationsGalleryScreenProps {
|
|
|
26
26
|
readonly config: CreationsConfig;
|
|
27
27
|
readonly t: (key: string) => string;
|
|
28
28
|
readonly locale?: string;
|
|
29
|
-
readonly enableEditing?: boolean;
|
|
30
|
-
readonly onImageEdit?: (uri: string, creationId: string) => void | Promise<void>;
|
|
31
29
|
readonly onEmptyAction?: () => void;
|
|
32
30
|
readonly emptyActionLabel?: string;
|
|
33
31
|
readonly showFilter?: boolean;
|
|
@@ -39,8 +37,6 @@ export function CreationsGalleryScreen({
|
|
|
39
37
|
repository,
|
|
40
38
|
config,
|
|
41
39
|
t,
|
|
42
|
-
enableEditing = false,
|
|
43
|
-
onImageEdit,
|
|
44
40
|
onEmptyAction,
|
|
45
41
|
emptyActionLabel,
|
|
46
42
|
showFilter = config.showFilter ?? true,
|
|
@@ -51,8 +47,6 @@ export function CreationsGalleryScreen({
|
|
|
51
47
|
const { share } = useSharing();
|
|
52
48
|
const alert = useAlert();
|
|
53
49
|
|
|
54
|
-
const [viewerVisible, setViewerVisible] = useState(false);
|
|
55
|
-
const [viewerIndex, setViewerIndex] = useState(0);
|
|
56
50
|
const [selectedCreation, setSelectedCreation] = useState<Creation | null>(null);
|
|
57
51
|
|
|
58
52
|
const { data: creations, isLoading, refetch } = useCreations({ userId, repository });
|
|
@@ -172,7 +166,6 @@ export function CreationsGalleryScreen({
|
|
|
172
166
|
showsVerticalScrollIndicator={false}
|
|
173
167
|
refreshControl={<RefreshControl refreshing={isLoading} onRefresh={() => void refetch()} tintColor={tokens.colors.primary} />}
|
|
174
168
|
/>
|
|
175
|
-
<CreationImageViewer creations={filters.filtered} visible={viewerVisible} index={viewerIndex} onDismiss={() => setViewerVisible(false)} onIndexChange={setViewerIndex} enableEditing={enableEditing} onImageEdit={onImageEdit} />
|
|
176
169
|
<FilterSheet visible={filters.statusFilterVisible} onClose={filters.closeStatusFilter} options={filters.statusFilter.filterOptions} selectedIds={[filters.statusFilter.selectedId]} onFilterPress={filters.statusFilter.selectFilter} onClearFilters={filters.statusFilter.clearFilter} title={t(config.translations.statusFilterTitle ?? "creations.filter.status")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
177
170
|
<FilterSheet visible={filters.mediaFilterVisible} onClose={filters.closeMediaFilter} options={filters.mediaFilter.filterOptions} selectedIds={[filters.mediaFilter.selectedId]} onFilterPress={filters.mediaFilter.selectFilter} onClearFilters={filters.mediaFilter.clearFilter} title={t(config.translations.mediaFilterTitle ?? "creations.filter.media")} clearLabel={t(config.translations.clearFilter ?? "common.clear")} />
|
|
178
171
|
</View>
|
|
@@ -13,12 +13,28 @@ import type {
|
|
|
13
13
|
} from "../types/result-preview.types";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Check if a string is a base64 data URL
|
|
16
|
+
* Check if a string is a base64 data URL or raw base64
|
|
17
17
|
*/
|
|
18
18
|
const isBase64DataUrl = (str: string): boolean => {
|
|
19
19
|
return str.startsWith("data:image/");
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Check if a string is raw base64 (not a URL and not a data URL)
|
|
24
|
+
*/
|
|
25
|
+
const isRawBase64 = (str: string): boolean => {
|
|
26
|
+
return !str.startsWith("http") && !str.startsWith("data:image/") && !str.startsWith("file://");
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert raw base64 to data URL format
|
|
31
|
+
*/
|
|
32
|
+
const toDataUrl = (str: string): string => {
|
|
33
|
+
if (isBase64DataUrl(str)) return str;
|
|
34
|
+
if (isRawBase64(str)) return `data:image/jpeg;base64,${str}`;
|
|
35
|
+
return str;
|
|
36
|
+
};
|
|
37
|
+
|
|
22
38
|
/**
|
|
23
39
|
* Save base64 image to file system
|
|
24
40
|
*/
|
|
@@ -80,11 +96,13 @@ export const useResultActions = (
|
|
|
80
96
|
throw new Error("Media library permission not granted");
|
|
81
97
|
}
|
|
82
98
|
|
|
83
|
-
|
|
99
|
+
// Convert to data URL if raw base64
|
|
100
|
+
const normalizedUrl = toDataUrl(imageUrl);
|
|
101
|
+
let fileUri = normalizedUrl;
|
|
84
102
|
|
|
85
103
|
// If it's a base64 string, save to file first
|
|
86
|
-
if (isBase64DataUrl(
|
|
87
|
-
fileUri = await saveBase64ToFile(
|
|
104
|
+
if (isBase64DataUrl(normalizedUrl)) {
|
|
105
|
+
fileUri = await saveBase64ToFile(normalizedUrl);
|
|
88
106
|
}
|
|
89
107
|
|
|
90
108
|
// Save to media library
|
|
@@ -112,11 +130,13 @@ export const useResultActions = (
|
|
|
112
130
|
setIsSharing(true);
|
|
113
131
|
onShareStart?.();
|
|
114
132
|
|
|
115
|
-
|
|
133
|
+
// Convert to data URL if raw base64
|
|
134
|
+
const normalizedUrl = toDataUrl(imageUrl);
|
|
135
|
+
let shareUrl = normalizedUrl;
|
|
116
136
|
|
|
117
137
|
// If it's a base64 string, save to file first for sharing
|
|
118
|
-
if (isBase64DataUrl(
|
|
119
|
-
shareUrl = await saveBase64ToFile(
|
|
138
|
+
if (isBase64DataUrl(normalizedUrl)) {
|
|
139
|
+
shareUrl = await saveBase64ToFile(normalizedUrl);
|
|
120
140
|
}
|
|
121
141
|
|
|
122
142
|
// Use expo-sharing for cross-platform file sharing
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
AtomicText,
|
|
9
9
|
AtomicIcon,
|
|
10
10
|
useAppDesignTokens,
|
|
11
|
+
type IconName,
|
|
11
12
|
} from "@umituz/react-native-design-system";
|
|
12
13
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
13
14
|
import { MESSAGE_TYPES } from "../../domain/constants";
|
|
@@ -41,7 +42,7 @@ export const CategoryGrid: React.FC<CategoryGridProps> = ({ onCategoryPress }) =
|
|
|
41
42
|
}]}
|
|
42
43
|
>
|
|
43
44
|
<View style={[styles.categoryIconContainer, { backgroundColor: `${tokens.colors.primary}15` }]}>
|
|
44
|
-
<AtomicIcon name={cat.icon as
|
|
45
|
+
<AtomicIcon name={cat.icon as IconName} color="primary" size="md" />
|
|
45
46
|
</View>
|
|
46
47
|
<AtomicText type="labelLarge" color="textPrimary">
|
|
47
48
|
{t(cat.labelKey)}
|
|
@@ -7,7 +7,6 @@ import { View, Pressable, StyleSheet } from "react-native";
|
|
|
7
7
|
import {
|
|
8
8
|
AtomicText,
|
|
9
9
|
AtomicIcon,
|
|
10
|
-
useAppDesignTokens,
|
|
11
10
|
useSafeAreaInsets,
|
|
12
11
|
} from "@umituz/react-native-design-system";
|
|
13
12
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
@@ -29,7 +28,6 @@ export const GeneratorHeader: React.FC<GeneratorHeaderProps> = ({
|
|
|
29
28
|
onGenerate,
|
|
30
29
|
isGenerating,
|
|
31
30
|
}) => {
|
|
32
|
-
const tokens = useAppDesignTokens();
|
|
33
31
|
const { top } = useSafeAreaInsets();
|
|
34
32
|
const { t } = useLocalization();
|
|
35
33
|
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
useAppDesignTokens,
|
|
12
12
|
} from "@umituz/react-native-design-system";
|
|
13
13
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
14
|
+
import type { TemplateMessage } from "../../domain/constants";
|
|
14
15
|
|
|
15
16
|
interface MessageListItemProps {
|
|
16
|
-
item:
|
|
17
|
+
item: TemplateMessage;
|
|
17
18
|
onShare: (text: string) => void;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicIcon,
|
|
11
11
|
useAppDesignTokens,
|
|
12
|
+
type IconName,
|
|
12
13
|
} from "@umituz/react-native-design-system";
|
|
13
14
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
14
15
|
import { MESSAGE_TONES } from "../../domain/constants";
|
|
@@ -55,7 +56,7 @@ export const ToneSelector: FC<ToneSelectorProps> = ({
|
|
|
55
56
|
]}
|
|
56
57
|
>
|
|
57
58
|
<AtomicIcon
|
|
58
|
-
name={config.icon as
|
|
59
|
+
name={config.icon as IconName}
|
|
59
60
|
color={isSelected ? "onPrimary" : "secondary"}
|
|
60
61
|
size="sm"
|
|
61
62
|
style={styles.icon}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AtomicText,
|
|
10
10
|
AtomicIcon,
|
|
11
11
|
useAppDesignTokens,
|
|
12
|
+
type IconName,
|
|
12
13
|
} from "@umituz/react-native-design-system";
|
|
13
14
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
14
15
|
import { MESSAGE_TYPES } from "../../domain/constants";
|
|
@@ -51,7 +52,7 @@ export const TypeSelector: FC<TypeSelectorProps> = ({
|
|
|
51
52
|
]}
|
|
52
53
|
>
|
|
53
54
|
<AtomicIcon
|
|
54
|
-
name={config.icon as
|
|
55
|
+
name={config.icon as IconName}
|
|
55
56
|
color={isSelected ? "onPrimary" : "secondary"}
|
|
56
57
|
size="md"
|
|
57
58
|
style={styles.icon}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useEffect } from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { useIsFocused } from "@react-navigation/native";
|
|
8
8
|
import { MessageType, MessageTone } from "../../domain/types";
|
|
9
9
|
import { generateLoveMessage } from "../../infrastructure/services/LoveMessageService";
|
|
10
10
|
import { PartnerProfileRepository } from "../../infrastructure/persistence/PartnerProfileRepository";
|
|
@@ -76,7 +76,7 @@ export const useLoveMessageGenerator = (config: {
|
|
|
76
76
|
|
|
77
77
|
setGeneratedMessage(message);
|
|
78
78
|
setCurrentStep(GeneratorStep.RESULT);
|
|
79
|
-
} catch
|
|
79
|
+
} catch {
|
|
80
80
|
// Error handled in usecase
|
|
81
81
|
} finally {
|
|
82
82
|
setIsGenerating(false);
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState, useEffect, useCallback } from "react";
|
|
6
|
-
import { useNavigation } from "@react-navigation/native";
|
|
7
6
|
import { PartnerProfile } from "../../domain/types";
|
|
8
7
|
import { PartnerProfileRepository } from "../../infrastructure/persistence/PartnerProfileRepository";
|
|
9
8
|
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { createStackNavigator } from "@react-navigation/stack";
|
|
8
|
-
import { LoveMessageExploreScreen } from "
|
|
9
|
-
import { MessageListScreen } from "
|
|
10
|
-
import { LoveMessageGeneratorScreen } from "
|
|
11
|
-
import { PartnerProfileScreen } from "
|
|
8
|
+
import { LoveMessageExploreScreen } from "../screens/LoveMessageExploreScreen";
|
|
9
|
+
import { MessageListScreen } from "../screens/MessageListScreen";
|
|
10
|
+
import { LoveMessageGeneratorScreen } from "../screens/LoveMessageGeneratorScreen";
|
|
11
|
+
import { PartnerProfileScreen } from "../screens/PartnerProfileScreen";
|
|
12
|
+
import type { MessageType } from "../../domain/types";
|
|
12
13
|
|
|
13
14
|
export type LoveMessageStackParamList = {
|
|
14
15
|
LoveMessageExplore: undefined;
|
|
15
|
-
MessageList: { categoryId: string }
|
|
16
|
-
MessageGenerator: { initialType?:
|
|
16
|
+
MessageList: { categoryId: string };
|
|
17
|
+
MessageGenerator: { initialType?: MessageType };
|
|
17
18
|
PartnerProfile: undefined;
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -21,19 +22,10 @@ const Stack = createStackNavigator<LoveMessageStackParamList>();
|
|
|
21
22
|
|
|
22
23
|
export const LoveMessageStack: React.FC = () => {
|
|
23
24
|
return (
|
|
24
|
-
<Stack.Navigator
|
|
25
|
-
|
|
26
|
-
initialRouteName="LoveMessageExplore"
|
|
27
|
-
>
|
|
28
|
-
<Stack.Screen
|
|
29
|
-
name="LoveMessageExplore"
|
|
30
|
-
component={LoveMessageExploreScreen}
|
|
31
|
-
/>
|
|
25
|
+
<Stack.Navigator screenOptions={{ headerShown: false }} initialRouteName="LoveMessageExplore">
|
|
26
|
+
<Stack.Screen name="LoveMessageExplore" component={LoveMessageExploreScreen} />
|
|
32
27
|
<Stack.Screen name="MessageList" component={MessageListScreen} />
|
|
33
|
-
<Stack.Screen
|
|
34
|
-
name="MessageGenerator"
|
|
35
|
-
component={LoveMessageGeneratorScreen}
|
|
36
|
-
/>
|
|
28
|
+
<Stack.Screen name="MessageGenerator" component={LoveMessageGeneratorScreen} />
|
|
37
29
|
<Stack.Screen name="PartnerProfile" component={PartnerProfileScreen} />
|
|
38
30
|
</Stack.Navigator>
|
|
39
31
|
);
|
|
@@ -3,53 +3,45 @@
|
|
|
3
3
|
* Premium entry point for Love Message domain
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { FC
|
|
7
|
-
import { View, ScrollView, StyleSheet
|
|
6
|
+
import { FC } from "react";
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
AtomicIcon,
|
|
11
9
|
useAppDesignTokens,
|
|
12
10
|
useSafeAreaInsets,
|
|
11
|
+
AppNavigation,
|
|
13
12
|
} from "@umituz/react-native-design-system";
|
|
14
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
15
|
-
import { useNavigation } from "@react-navigation/native";
|
|
16
13
|
import { ExploreHeader } from "../components/ExploreHeader";
|
|
17
14
|
import { LoveMessageHeroSection } from "../components/LoveMessageHeroSection";
|
|
18
15
|
import { CategoryGrid } from "../components/CategoryGrid";
|
|
19
16
|
import { TrendingSection } from "../components/TrendingSection";
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
onNavigateToGenerator: () => void;
|
|
23
|
-
onNavigateToCategory: (categoryId: string) => void;
|
|
24
|
-
onNavigateToTrending: () => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const LoveMessageExploreScreen: FC<LoveMessageExploreScreenProps> = ({
|
|
28
|
-
onNavigateToGenerator,
|
|
29
|
-
onNavigateToCategory,
|
|
30
|
-
onNavigateToTrending,
|
|
31
|
-
}) => {
|
|
18
|
+
export const LoveMessageExploreScreen: FC = () => {
|
|
32
19
|
const tokens = useAppDesignTokens();
|
|
33
20
|
const { bottom } = useSafeAreaInsets();
|
|
34
|
-
|
|
21
|
+
|
|
22
|
+
const handleNavigateToGenerator = () => {
|
|
23
|
+
AppNavigation.navigate("MessageGenerator", {});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const handleNavigateToCategory = (categoryId: string) => {
|
|
27
|
+
AppNavigation.navigate("MessageList", { categoryId });
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleNavigateToTrending = () => {
|
|
31
|
+
AppNavigation.navigate("MessageList", { categoryId: "trending" });
|
|
32
|
+
};
|
|
35
33
|
|
|
36
34
|
return (
|
|
37
35
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
38
|
-
<ExploreHeader onMagicPress={
|
|
36
|
+
<ExploreHeader onMagicPress={handleNavigateToGenerator} />
|
|
39
37
|
|
|
40
|
-
<ScrollView
|
|
38
|
+
<ScrollView
|
|
41
39
|
showsVerticalScrollIndicator={false}
|
|
42
40
|
contentContainerStyle={{ paddingBottom: bottom + 120 }}
|
|
43
41
|
>
|
|
44
42
|
<LoveMessageHeroSection />
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
onCategoryPress={onNavigateToCategory}
|
|
48
|
-
/>
|
|
49
|
-
|
|
50
|
-
<TrendingSection
|
|
51
|
-
onViewAll={onNavigateToTrending}
|
|
52
|
-
/>
|
|
43
|
+
<CategoryGrid onCategoryPress={handleNavigateToCategory} />
|
|
44
|
+
<TrendingSection onViewAll={handleNavigateToTrending} />
|
|
53
45
|
</ScrollView>
|
|
54
46
|
</View>
|
|
55
47
|
);
|
|
@@ -57,19 +49,4 @@ export const LoveMessageExploreScreen: FC<LoveMessageExploreScreenProps> = ({
|
|
|
57
49
|
|
|
58
50
|
const styles = StyleSheet.create({
|
|
59
51
|
container: { flex: 1 },
|
|
60
|
-
fabContainer: {
|
|
61
|
-
position: 'absolute',
|
|
62
|
-
left: 0,
|
|
63
|
-
right: 0,
|
|
64
|
-
alignItems: 'center',
|
|
65
|
-
zIndex: 10,
|
|
66
|
-
},
|
|
67
|
-
fab: {
|
|
68
|
-
width: 64,
|
|
69
|
-
height: 64,
|
|
70
|
-
borderRadius: 32,
|
|
71
|
-
alignItems: 'center',
|
|
72
|
-
justifyContent: 'center',
|
|
73
|
-
elevation: 8,
|
|
74
|
-
},
|
|
75
52
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Love Message Generator Screen
|
|
3
|
-
*
|
|
3
|
+
* Multi-step wizard flow
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { FC, useMemo } from "react";
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
AtomicButton,
|
|
11
11
|
useAppDesignTokens,
|
|
12
12
|
useSafeAreaInsets,
|
|
13
|
+
AppNavigation,
|
|
13
14
|
} from "@umituz/react-native-design-system";
|
|
14
15
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
16
|
+
import { useRoute, RouteProp } from "@react-navigation/native";
|
|
15
17
|
import { ProgressDots } from "../components/ProgressDots";
|
|
16
18
|
import { MessageResult } from "../components/MessageResult";
|
|
17
19
|
import { GeneratorHeader } from "../components/GeneratorHeader";
|
|
@@ -21,35 +23,37 @@ import { StepDetails } from "../components/StepDetails";
|
|
|
21
23
|
import { MessageType } from "../../domain/types";
|
|
22
24
|
import { useLoveMessageGenerator, GeneratorStep } from "../hooks/useLoveMessageGenerator";
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
onBack: () => void;
|
|
26
|
-
onNavigateToProfile: () => void;
|
|
27
|
-
initialType?: MessageType;
|
|
28
|
-
}
|
|
26
|
+
type RouteParams = { initialType?: MessageType };
|
|
29
27
|
|
|
30
|
-
export const LoveMessageGeneratorScreen: FC
|
|
31
|
-
onBack,
|
|
32
|
-
onNavigateToProfile,
|
|
33
|
-
initialType,
|
|
34
|
-
}) => {
|
|
28
|
+
export const LoveMessageGeneratorScreen: FC = () => {
|
|
35
29
|
const tokens = useAppDesignTokens();
|
|
36
30
|
const { bottom } = useSafeAreaInsets();
|
|
37
31
|
const { t } = useLocalization();
|
|
38
|
-
const
|
|
32
|
+
const route = useRoute<RouteProp<{ params: RouteParams }, "params">>();
|
|
33
|
+
|
|
34
|
+
const initialType = route.params?.initialType;
|
|
35
|
+
const gen = useLoveMessageGenerator({ onBack: () => AppNavigation.goBack(), initialType });
|
|
36
|
+
|
|
37
|
+
const handleNavigateToProfile = () => AppNavigation.navigate("PartnerProfile");
|
|
39
38
|
|
|
40
39
|
const stepTitle = useMemo(() => {
|
|
41
40
|
switch (gen.currentStep) {
|
|
42
|
-
case GeneratorStep.PARTNER:
|
|
43
|
-
|
|
44
|
-
case GeneratorStep.
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
case GeneratorStep.PARTNER:
|
|
42
|
+
return t("loveMessage.generator.stepPartner");
|
|
43
|
+
case GeneratorStep.VIBE:
|
|
44
|
+
return t("loveMessage.generator.stepVibe");
|
|
45
|
+
case GeneratorStep.DETAILS:
|
|
46
|
+
return t("loveMessage.generator.stepDetails");
|
|
47
|
+
case GeneratorStep.RESULT:
|
|
48
|
+
return t("loveMessage.generator.stepResult");
|
|
49
|
+
default:
|
|
50
|
+
return "";
|
|
47
51
|
}
|
|
48
52
|
}, [gen.currentStep, t]);
|
|
49
53
|
|
|
50
54
|
return (
|
|
51
55
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
52
|
-
<GeneratorHeader
|
|
56
|
+
<GeneratorHeader
|
|
53
57
|
currentStep={gen.currentStep}
|
|
54
58
|
partnerName={gen.partnerName}
|
|
55
59
|
onBack={gen.handleBack}
|
|
@@ -58,7 +62,10 @@ export const LoveMessageGeneratorScreen: FC<LoveMessageGeneratorScreenProps> = (
|
|
|
58
62
|
isGenerating={gen.isGenerating}
|
|
59
63
|
/>
|
|
60
64
|
|
|
61
|
-
<ScrollView
|
|
65
|
+
<ScrollView
|
|
66
|
+
showsVerticalScrollIndicator={false}
|
|
67
|
+
contentContainerStyle={{ paddingBottom: bottom + 120 }}
|
|
68
|
+
>
|
|
62
69
|
<View style={styles.heroSection}>
|
|
63
70
|
<ProgressDots currentStep={gen.currentStep} totalSteps={4} />
|
|
64
71
|
<AtomicText type="headlineLarge" color="textPrimary" style={styles.heroTitle}>
|
|
@@ -69,20 +76,20 @@ export const LoveMessageGeneratorScreen: FC<LoveMessageGeneratorScreenProps> = (
|
|
|
69
76
|
<View style={styles.formContent}>
|
|
70
77
|
{gen.currentStep === GeneratorStep.PARTNER && (
|
|
71
78
|
<Animated.View>
|
|
72
|
-
<StepPartner
|
|
79
|
+
<StepPartner
|
|
73
80
|
partnerName={gen.partnerName}
|
|
74
81
|
setPartnerName={gen.setPartnerName}
|
|
75
82
|
useProfile={gen.useProfile}
|
|
76
83
|
setUseProfile={gen.setUseProfile}
|
|
77
84
|
hasProfile={gen.hasProfile}
|
|
78
|
-
onEditProfile={
|
|
85
|
+
onEditProfile={handleNavigateToProfile}
|
|
79
86
|
/>
|
|
80
87
|
</Animated.View>
|
|
81
88
|
)}
|
|
82
89
|
|
|
83
90
|
{gen.currentStep === GeneratorStep.VIBE && (
|
|
84
91
|
<Animated.View>
|
|
85
|
-
<StepVibe
|
|
92
|
+
<StepVibe
|
|
86
93
|
selectedType={gen.selectedType}
|
|
87
94
|
setSelectedType={gen.setSelectedType}
|
|
88
95
|
selectedTone={gen.selectedTone}
|
|
@@ -120,9 +127,20 @@ export const LoveMessageGeneratorScreen: FC<LoveMessageGeneratorScreenProps> = (
|
|
|
120
127
|
|
|
121
128
|
const styles = StyleSheet.create({
|
|
122
129
|
container: { flex: 1 },
|
|
123
|
-
heroSection: { paddingVertical: 32, alignItems:
|
|
124
|
-
heroTitle: {
|
|
130
|
+
heroSection: { paddingVertical: 32, alignItems: "center" },
|
|
131
|
+
heroTitle: {
|
|
132
|
+
fontWeight: "bold",
|
|
133
|
+
textAlign: "center",
|
|
134
|
+
marginBottom: 8,
|
|
135
|
+
paddingHorizontal: 20,
|
|
136
|
+
},
|
|
125
137
|
formContent: { paddingHorizontal: 20 },
|
|
126
|
-
footer: {
|
|
138
|
+
footer: {
|
|
139
|
+
position: "absolute",
|
|
140
|
+
bottom: 0,
|
|
141
|
+
left: 0,
|
|
142
|
+
right: 0,
|
|
143
|
+
padding: 24,
|
|
144
|
+
},
|
|
127
145
|
actionBtn: { height: 60, borderRadius: 30 },
|
|
128
146
|
});
|
|
@@ -9,60 +9,67 @@ import {
|
|
|
9
9
|
AtomicIcon,
|
|
10
10
|
useAppDesignTokens,
|
|
11
11
|
useSafeAreaInsets,
|
|
12
|
+
AppNavigation,
|
|
12
13
|
} from "@umituz/react-native-design-system";
|
|
13
14
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
14
|
-
import {
|
|
15
|
+
import { useRoute, RouteProp } from "@react-navigation/native";
|
|
15
16
|
import { CATEGORY_TEMPLATES, MESSAGE_TYPES } from "../../domain/constants";
|
|
16
17
|
import { MessageListItem } from "../components/MessageListItem";
|
|
18
|
+
import type { MessageType } from "../../domain/types";
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
categoryId: string;
|
|
20
|
-
onBack: () => void;
|
|
21
|
-
onNavigateToGenerator: (initialType?: string) => void;
|
|
22
|
-
}
|
|
20
|
+
type RouteParams = { categoryId?: string };
|
|
23
21
|
|
|
24
|
-
export const MessageListScreen: FC
|
|
25
|
-
categoryId,
|
|
26
|
-
onBack,
|
|
27
|
-
onNavigateToGenerator,
|
|
28
|
-
}) => {
|
|
22
|
+
export const MessageListScreen: FC = () => {
|
|
29
23
|
const tokens = useAppDesignTokens();
|
|
30
24
|
const { top, bottom } = useSafeAreaInsets();
|
|
31
25
|
const { t } = useLocalization();
|
|
26
|
+
const route = useRoute<RouteProp<{ params: RouteParams }, "params">>();
|
|
32
27
|
|
|
28
|
+
const categoryId = route.params?.categoryId ?? "romantic";
|
|
33
29
|
const messages = useMemo(() => CATEGORY_TEMPLATES[categoryId] || [], [categoryId]);
|
|
34
30
|
|
|
35
31
|
const categoryTitle = useMemo(() => {
|
|
36
32
|
if (categoryId === "trending") return t("loveMessage.explore.trending");
|
|
37
|
-
const config = MESSAGE_TYPES.find(c => c.type === categoryId);
|
|
33
|
+
const config = MESSAGE_TYPES.find((c) => c.type === categoryId);
|
|
38
34
|
return config ? t(config.labelKey) : categoryId;
|
|
39
35
|
}, [categoryId, t]);
|
|
40
36
|
|
|
37
|
+
const handleBack = () => AppNavigation.goBack();
|
|
38
|
+
|
|
39
|
+
const handleNavigateToGenerator = (type?: string) => {
|
|
40
|
+
AppNavigation.navigate("MessageGenerator", { initialType: type as MessageType });
|
|
41
|
+
};
|
|
42
|
+
|
|
41
43
|
const handleShare = useCallback(async (text: string) => {
|
|
42
44
|
try {
|
|
43
45
|
await Share.share({ message: text });
|
|
44
|
-
} catch {
|
|
46
|
+
} catch {
|
|
47
|
+
/* Ignore */
|
|
48
|
+
}
|
|
45
49
|
}, []);
|
|
46
50
|
|
|
47
51
|
return (
|
|
48
52
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
49
53
|
<View style={[styles.header, { paddingTop: top + tokens.spacing.md }]}>
|
|
50
|
-
<Pressable onPress={
|
|
54
|
+
<Pressable onPress={handleBack} style={styles.backBtn}>
|
|
51
55
|
<AtomicIcon name="arrow-back" color="textPrimary" size="sm" />
|
|
52
56
|
</Pressable>
|
|
53
57
|
<AtomicText type="headlineSmall" color="textPrimary" style={styles.headerTitle}>
|
|
54
58
|
{categoryTitle}
|
|
55
59
|
</AtomicText>
|
|
56
|
-
<View style={
|
|
60
|
+
<View style={styles.spacer} />
|
|
57
61
|
</View>
|
|
58
62
|
|
|
59
|
-
<ScrollView
|
|
63
|
+
<ScrollView
|
|
64
|
+
showsVerticalScrollIndicator={false}
|
|
65
|
+
contentContainerStyle={{ paddingBottom: bottom + 100, paddingHorizontal: 16 }}
|
|
66
|
+
>
|
|
60
67
|
<View style={styles.headlineContainer}>
|
|
61
68
|
<AtomicText type="headlineMedium" color="textPrimary" style={styles.headline}>
|
|
62
|
-
{`${messages.length} messages`}
|
|
69
|
+
{`${messages.length} ${t("loveMessage.messages")}`}
|
|
63
70
|
</AtomicText>
|
|
64
71
|
<AtomicText type="bodySmall" color="textTertiary">
|
|
65
|
-
|
|
72
|
+
{t("loveMessage.aiCrafted")}
|
|
66
73
|
</AtomicText>
|
|
67
74
|
</View>
|
|
68
75
|
|
|
@@ -72,13 +79,13 @@ export const MessageListScreen: FC<MessageListScreenProps> = ({
|
|
|
72
79
|
</ScrollView>
|
|
73
80
|
|
|
74
81
|
<View style={[styles.fabContainer, { bottom: bottom + tokens.spacing.lg }]}>
|
|
75
|
-
<Pressable
|
|
76
|
-
onPress={() =>
|
|
82
|
+
<Pressable
|
|
83
|
+
onPress={() => handleNavigateToGenerator(categoryId !== "trending" ? categoryId : undefined)}
|
|
77
84
|
style={[styles.fab, { backgroundColor: tokens.colors.primary }]}
|
|
78
85
|
>
|
|
79
86
|
<AtomicIcon name="sparkles" color="onPrimary" size="sm" />
|
|
80
87
|
<AtomicText type="labelLarge" color="onPrimary" style={styles.fabText}>
|
|
81
|
-
|
|
88
|
+
{t("loveMessage.generateMore")}
|
|
82
89
|
</AtomicText>
|
|
83
90
|
</Pressable>
|
|
84
91
|
</View>
|
|
@@ -88,12 +95,32 @@ export const MessageListScreen: FC<MessageListScreenProps> = ({
|
|
|
88
95
|
|
|
89
96
|
const styles = StyleSheet.create({
|
|
90
97
|
container: { flex: 1 },
|
|
91
|
-
header: {
|
|
98
|
+
header: {
|
|
99
|
+
flexDirection: "row",
|
|
100
|
+
justifyContent: "space-between",
|
|
101
|
+
alignItems: "center",
|
|
102
|
+
paddingHorizontal: 8,
|
|
103
|
+
paddingBottom: 16,
|
|
104
|
+
},
|
|
92
105
|
backBtn: { padding: 12 },
|
|
93
|
-
headerTitle: { fontWeight:
|
|
106
|
+
headerTitle: { fontWeight: "bold" },
|
|
107
|
+
spacer: { width: 44 },
|
|
94
108
|
headlineContainer: { paddingVertical: 24 },
|
|
95
|
-
headline: { fontWeight:
|
|
96
|
-
fabContainer: {
|
|
97
|
-
|
|
109
|
+
headline: { fontWeight: "bold", marginBottom: 4 },
|
|
110
|
+
fabContainer: {
|
|
111
|
+
position: "absolute",
|
|
112
|
+
left: 20,
|
|
113
|
+
right: 20,
|
|
114
|
+
alignItems: "center",
|
|
115
|
+
},
|
|
116
|
+
fab: {
|
|
117
|
+
flexDirection: "row",
|
|
118
|
+
alignItems: "center",
|
|
119
|
+
justifyContent: "center",
|
|
120
|
+
paddingVertical: 16,
|
|
121
|
+
paddingHorizontal: 24,
|
|
122
|
+
borderRadius: 32,
|
|
123
|
+
gap: 12,
|
|
124
|
+
},
|
|
98
125
|
fabText: { fontWeight: "bold", letterSpacing: 1 },
|
|
99
126
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Partner Profile Screen
|
|
3
|
-
* Optimized version with hooks and components
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import { FC } from "react";
|
|
@@ -10,28 +9,25 @@ import {
|
|
|
10
9
|
AtomicButton,
|
|
11
10
|
useAppDesignTokens,
|
|
12
11
|
useSafeAreaInsets,
|
|
12
|
+
AppNavigation,
|
|
13
13
|
} from "@umituz/react-native-design-system";
|
|
14
14
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
15
15
|
import { LOVE_LANGUAGES } from "../../domain/constants";
|
|
16
16
|
import { usePartnerProfile } from "../hooks/usePartnerProfile";
|
|
17
17
|
import { FieldInput } from "../components/FieldInput";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
onBack: () => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const PartnerProfileScreen: FC<PartnerProfileScreenProps> = ({ onBack }) => {
|
|
19
|
+
export const PartnerProfileScreen: FC = () => {
|
|
24
20
|
const tokens = useAppDesignTokens();
|
|
25
21
|
const { top, bottom } = useSafeAreaInsets();
|
|
26
22
|
const { t } = useLocalization();
|
|
27
|
-
const p = usePartnerProfile(
|
|
23
|
+
const p = usePartnerProfile(() => AppNavigation.goBack());
|
|
28
24
|
|
|
29
25
|
if (p.isLoading) return null;
|
|
30
26
|
|
|
31
27
|
return (
|
|
32
28
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
33
29
|
<View style={[styles.header, { paddingTop: top + tokens.spacing.md }]}>
|
|
34
|
-
<AtomicButton icon="arrow-back" onPress={
|
|
30
|
+
<AtomicButton icon="arrow-back" onPress={() => AppNavigation.goBack()} variant="text" size="sm" />
|
|
35
31
|
<View style={styles.headerTitle}>
|
|
36
32
|
<AtomicText type="headlineSmall" color="textPrimary" style={styles.headerText}>
|
|
37
33
|
{t("loveMessage.partnerProfile.title")}
|
|
@@ -39,17 +35,21 @@ export const PartnerProfileScreen: FC<PartnerProfileScreenProps> = ({ onBack })
|
|
|
39
35
|
</View>
|
|
40
36
|
</View>
|
|
41
37
|
|
|
42
|
-
<ScrollView
|
|
43
|
-
contentContainerStyle={{
|
|
38
|
+
<ScrollView
|
|
39
|
+
contentContainerStyle={{
|
|
40
|
+
paddingBottom: bottom + tokens.spacing.xl,
|
|
41
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
42
|
+
}}
|
|
44
43
|
showsVerticalScrollIndicator={false}
|
|
45
44
|
>
|
|
46
45
|
<AtomicText type="bodyMedium" color="textSecondary" style={styles.subtitle}>
|
|
47
46
|
{t("loveMessage.partnerProfile.subtitle")}
|
|
48
47
|
</AtomicText>
|
|
49
48
|
|
|
50
|
-
<FieldInput
|
|
51
|
-
label={t("loveMessage.partnerName")}
|
|
52
|
-
|
|
49
|
+
<FieldInput
|
|
50
|
+
label={t("loveMessage.partnerName")}
|
|
51
|
+
value={p.profile.name}
|
|
52
|
+
onChange={(text) => p.setProfile((prev) => ({ ...prev, name: text }))}
|
|
53
53
|
placeholder={t("loveMessage.partnerNamePlaceholder")}
|
|
54
54
|
/>
|
|
55
55
|
|
|
@@ -60,30 +60,39 @@ export const PartnerProfileScreen: FC<PartnerProfileScreenProps> = ({ onBack })
|
|
|
60
60
|
<View style={styles.chipContainer}>
|
|
61
61
|
{LOVE_LANGUAGES.map((lang) => (
|
|
62
62
|
<AtomicButton
|
|
63
|
-
key={lang.language}
|
|
63
|
+
key={lang.language}
|
|
64
|
+
title={t(lang.labelKey)}
|
|
64
65
|
onPress={() => p.setProfile((prev) => ({ ...prev, loveLanguage: lang.language }))}
|
|
65
66
|
variant={p.profile.loveLanguage === lang.language ? "primary" : "outline"}
|
|
66
|
-
size="sm"
|
|
67
|
+
size="sm"
|
|
68
|
+
style={styles.chip}
|
|
67
69
|
/>
|
|
68
70
|
))}
|
|
69
71
|
</View>
|
|
70
72
|
</View>
|
|
71
73
|
|
|
72
|
-
<FieldInput
|
|
73
|
-
label={t("loveMessage.partnerProfile.traits")}
|
|
74
|
-
|
|
74
|
+
<FieldInput
|
|
75
|
+
label={t("loveMessage.partnerProfile.traits")}
|
|
76
|
+
value={p.profile.traits}
|
|
77
|
+
onChange={(text) => p.setProfile((prev) => ({ ...prev, traits: text }))}
|
|
75
78
|
placeholder={t("loveMessage.partnerProfile.traitsPlaceholder")}
|
|
76
79
|
/>
|
|
77
80
|
|
|
78
|
-
<FieldInput
|
|
79
|
-
label={t("loveMessage.partnerProfile.quirks")}
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
<FieldInput
|
|
82
|
+
label={t("loveMessage.partnerProfile.quirks")}
|
|
83
|
+
value={p.profile.quirks}
|
|
84
|
+
onChange={(text) => p.setProfile((prev) => ({ ...prev, quirks: text }))}
|
|
85
|
+
placeholder={t("loveMessage.partnerProfile.quirksPlaceholder")}
|
|
86
|
+
multiline
|
|
82
87
|
/>
|
|
83
88
|
|
|
84
89
|
<AtomicButton
|
|
85
|
-
title={t("loveMessage.partnerProfile.save")}
|
|
86
|
-
|
|
90
|
+
title={t("loveMessage.partnerProfile.save")}
|
|
91
|
+
onPress={p.handleSave}
|
|
92
|
+
disabled={!p.profile.name.trim()}
|
|
93
|
+
variant="primary"
|
|
94
|
+
fullWidth
|
|
95
|
+
style={styles.saveBtn}
|
|
87
96
|
/>
|
|
88
97
|
</ScrollView>
|
|
89
98
|
</View>
|
|
@@ -92,7 +101,12 @@ export const PartnerProfileScreen: FC<PartnerProfileScreenProps> = ({ onBack })
|
|
|
92
101
|
|
|
93
102
|
const styles = StyleSheet.create({
|
|
94
103
|
container: { flex: 1 },
|
|
95
|
-
header: {
|
|
104
|
+
header: {
|
|
105
|
+
flexDirection: "row",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
paddingHorizontal: 8,
|
|
108
|
+
paddingBottom: 24,
|
|
109
|
+
},
|
|
96
110
|
headerTitle: { flex: 1, marginRight: 40, alignItems: "center" },
|
|
97
111
|
headerText: { fontWeight: "bold" },
|
|
98
112
|
subtitle: { marginBottom: 32 },
|