@umituz/react-native-ai-generation-content 1.17.271 → 1.17.272

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.271",
3
+ "version": "1.17.272",
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",
@@ -42,7 +42,7 @@ export const CreationRating: React.FC<CreationRatingProps> = ({
42
42
  })}
43
43
  </View>
44
44
  {!readonly && rating > 0 && (
45
- <AtomicText variant="bodySmall" color="textSecondary" style={styles.valueText}>
45
+ <AtomicText type="bodySmall" color="textSecondary" style={styles.valueText}>
46
46
  {rating} / {max}
47
47
  </AtomicText>
48
48
  )}
@@ -32,9 +32,3 @@ export { GalleryEmptyStates } from "./GalleryEmptyStates";
32
32
  export { CreationsHomeCard } from "./CreationsHomeCard";
33
33
  export { CreationRating } from "./CreationRating";
34
34
  export { CreationsGrid } from "./CreationsGrid";
35
-
36
- // Detail Components
37
- export { DetailHeader } from "./CreationDetail/DetailHeader";
38
- export { DetailImage } from "./CreationDetail/DetailImage";
39
- export { DetailStory } from "./CreationDetail/DetailStory";
40
- export { DetailActions } from "./CreationDetail/DetailActions";
@@ -3,7 +3,7 @@
3
3
  * Handles rating of creations with optimistic update
4
4
  */
5
5
 
6
- import { useOptimisticUpdate } from "@umituz/react-native-design-system";
6
+ import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
7
7
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
8
8
  import type { Creation } from "../../domain/entities/Creation";
9
9
 
@@ -12,19 +12,44 @@ interface UseCreationRatingProps {
12
12
  readonly repository: ICreationsRepository;
13
13
  }
14
14
 
15
+ interface RatingVariables {
16
+ readonly id: string;
17
+ readonly rating: number;
18
+ }
19
+
15
20
  export function useCreationRating({
16
21
  userId,
17
22
  repository,
18
23
  }: UseCreationRatingProps) {
19
- return useOptimisticUpdate<boolean, { id: string; rating: number }, Creation[]>({
20
- mutationFn: async ({ id, rating }) => {
24
+ const queryClient = useQueryClient();
25
+ const queryKey = ["creations", userId ?? ""];
26
+
27
+ return useMutation({
28
+ mutationFn: async ({ id, rating }: RatingVariables) => {
21
29
  if (!userId) return false;
22
30
  return repository.rate(userId, id, rating);
23
31
  },
24
- queryKey: ["creations", userId ?? ""],
25
- updateFn: (old, { id, rating }) =>
26
- old?.map((c) =>
27
- c.id === id ? { ...c, rating, ratedAt: new Date() } : c
28
- ) ?? [],
32
+ onMutate: async ({ id, rating }: RatingVariables) => {
33
+ await queryClient.cancelQueries({ queryKey });
34
+ const previousData = queryClient.getQueryData<Creation[]>(queryKey);
35
+
36
+ if (previousData) {
37
+ queryClient.setQueryData<Creation[]>(queryKey, (old) =>
38
+ old?.map((c) =>
39
+ c.id === id ? { ...c, rating, ratedAt: new Date() } : c
40
+ ) ?? []
41
+ );
42
+ }
43
+
44
+ return { previousData };
45
+ },
46
+ onError: (_error, _variables, context) => {
47
+ if (context?.previousData) {
48
+ queryClient.setQueryData(queryKey, context.previousData);
49
+ }
50
+ },
51
+ onSettled: () => {
52
+ void queryClient.invalidateQueries({ queryKey });
53
+ },
29
54
  });
30
55
  }
@@ -3,7 +3,7 @@
3
3
  * Fetches user's creations from repository
4
4
  */
5
5
 
6
- import { useAppQuery } from "@umituz/react-native-design-system";
6
+ import { useQuery } from "@umituz/react-native-design-system";
7
7
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
8
8
  import type { Creation } from "../../domain/entities/Creation";
9
9
 
@@ -23,7 +23,7 @@ export function useCreations({
23
23
  repository,
24
24
  enabled = true,
25
25
  }: UseCreationsProps) {
26
- return useAppQuery<Creation[]>({
26
+ return useQuery<Creation[]>({
27
27
  queryKey: ["creations", userId ?? ""],
28
28
  queryFn: async () => {
29
29
  if (!userId) {
@@ -3,7 +3,7 @@
3
3
  * Handles deletion of user creations with optimistic update
4
4
  */
5
5
 
6
- import { useOptimisticUpdate } from "@umituz/react-native-design-system";
6
+ import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
7
7
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
8
8
  import type { Creation } from "../../domain/entities/Creation";
9
9
 
@@ -16,12 +16,33 @@ export function useDeleteCreation({
16
16
  userId,
17
17
  repository,
18
18
  }: UseDeleteCreationProps) {
19
- return useOptimisticUpdate<boolean, string, Creation[]>({
19
+ const queryClient = useQueryClient();
20
+ const queryKey = ["creations", userId ?? ""];
21
+
22
+ return useMutation({
20
23
  mutationFn: async (creationId: string) => {
21
24
  if (!userId) return false;
22
25
  return repository.delete(userId, creationId);
23
26
  },
24
- queryKey: ["creations", userId ?? ""],
25
- updateFn: (old, creationId) => old?.filter((c) => c.id !== creationId) ?? [],
27
+ onMutate: async (creationId: string) => {
28
+ await queryClient.cancelQueries({ queryKey });
29
+ const previousData = queryClient.getQueryData<Creation[]>(queryKey);
30
+
31
+ if (previousData) {
32
+ queryClient.setQueryData<Creation[]>(queryKey, (old) =>
33
+ old?.filter((c) => c.id !== creationId) ?? []
34
+ );
35
+ }
36
+
37
+ return { previousData };
38
+ },
39
+ onError: (_error, _variables, context) => {
40
+ if (context?.previousData) {
41
+ queryClient.setQueryData(queryKey, context.previousData);
42
+ }
43
+ },
44
+ onSettled: () => {
45
+ void queryClient.invalidateQueries({ queryKey });
46
+ },
26
47
  });
27
48
  }
@@ -5,9 +5,10 @@
5
5
 
6
6
  import React, { useMemo, useCallback, useState } from "react";
7
7
  import {
8
- View as RNView,
9
- FlatList as RNFlatList,
8
+ View,
9
+ FlatList,
10
10
  StyleSheet,
11
+ type ListRenderItemInfo,
11
12
  } from "react-native";
12
13
  import {
13
14
  useAppDesignTokens,
@@ -22,9 +23,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
22
23
  import type { ScenarioData, ScenarioCategory } from "../../domain/types";
23
24
  import { SCENARIO_DEFAULTS } from "../../domain/types";
24
25
 
25
- const View = RNView as any;
26
- const FlatList = RNFlatList as any;
27
-
28
26
  export interface ScenarioGridProps {
29
27
  readonly scenarios: readonly ScenarioData[];
30
28
  readonly selectedScenarioId: string | null;
@@ -148,7 +146,7 @@ export const ScenarioGrid: React.FC<ScenarioGridProps> = ({
148
146
  );
149
147
 
150
148
  const renderItem = useCallback(
151
- ({ item }: { item: ScenarioData }) => {
149
+ ({ item }: ListRenderItemInfo<ScenarioData>) => {
152
150
  const title = t(`scenario.${item.id}.title`);
153
151
  const description = t(`scenario.${item.id}.description`);
154
152
 
@@ -91,7 +91,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
91
91
  ]}
92
92
  >
93
93
  <AtomicIcon
94
- name={style.icon as any}
94
+ name={style.icon}
95
95
  size="md"
96
96
  color={selectedStyle === style.id ? "primary" : "textSecondary"}
97
97
  />
@@ -57,7 +57,7 @@ export const ScenarioSelectorScreen: React.FC<ScenarioSelectorScreenProps> = ({
57
57
  );
58
58
  };
59
59
 
60
- const createStyles = (tokens: DesignTokens) =>
60
+ const createStyles = (_tokens: DesignTokens) =>
61
61
  StyleSheet.create({
62
62
  container: {
63
63
  flex: 1,
@@ -1,95 +0,0 @@
1
-
2
- import React from 'react';
3
- import { View, StyleSheet, TouchableOpacity } from 'react-native';
4
- import { AtomicText, AtomicIcon, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
5
-
6
- interface DetailActionsProps {
7
- readonly onShare: () => void;
8
- readonly onDelete: () => void;
9
- readonly onViewResult?: () => void;
10
- readonly shareLabel: string;
11
- readonly deleteLabel: string;
12
- readonly viewResultLabel?: string;
13
- }
14
-
15
- export const DetailActions: React.FC<DetailActionsProps> = ({
16
- onShare,
17
- onDelete,
18
- onViewResult,
19
- shareLabel,
20
- deleteLabel,
21
- viewResultLabel
22
- }) => {
23
- const tokens = useAppDesignTokens();
24
- const styles = useStyles(tokens);
25
-
26
- return (
27
- <View style={styles.container}>
28
- {onViewResult && viewResultLabel && (
29
- <TouchableOpacity
30
- style={[styles.button, styles.viewResultButton]}
31
- onPress={onViewResult}
32
- activeOpacity={0.7}
33
- >
34
- <AtomicIcon name="eye-outline" size="sm" color="onPrimary" />
35
- <AtomicText style={styles.buttonText}>{viewResultLabel}</AtomicText>
36
- </TouchableOpacity>
37
- )}
38
-
39
- <TouchableOpacity
40
- style={[styles.button, styles.shareButton]}
41
- onPress={onShare}
42
- activeOpacity={0.7}
43
- >
44
- <AtomicIcon name="share-social-outline" size="sm" color="onPrimary" />
45
- <AtomicText style={styles.buttonText}>{shareLabel}</AtomicText>
46
- </TouchableOpacity>
47
-
48
- <TouchableOpacity
49
- style={[styles.button, styles.deleteButton]}
50
- onPress={onDelete}
51
- activeOpacity={0.7}
52
- >
53
- <AtomicIcon name="trash-outline" size="sm" color="error" />
54
- <AtomicText style={[styles.buttonText, { color: tokens.colors.error }]}>
55
- {deleteLabel}
56
- </AtomicText>
57
- </TouchableOpacity>
58
- </View>
59
- );
60
- };
61
-
62
- const useStyles = (tokens: DesignTokens) => StyleSheet.create({
63
- container: {
64
- flexDirection: 'row',
65
- justifyContent: 'center',
66
- flexWrap: 'wrap',
67
- gap: tokens.spacing.md,
68
- paddingHorizontal: tokens.spacing.lg,
69
- marginBottom: tokens.spacing.xxl,
70
- },
71
- button: {
72
- flexDirection: 'row',
73
- alignItems: 'center',
74
- justifyContent: 'center',
75
- paddingVertical: 12,
76
- paddingHorizontal: 20,
77
- borderRadius: 16,
78
- gap: 8,
79
- minWidth: 110,
80
- },
81
- viewResultButton: {
82
- backgroundColor: tokens.colors.primary,
83
- },
84
- shareButton: {
85
- backgroundColor: tokens.colors.primary,
86
- },
87
- deleteButton: {
88
- backgroundColor: tokens.colors.error + '10',
89
- },
90
- buttonText: {
91
- fontWeight: '700',
92
- color: tokens.colors.textInverse,
93
- fontSize: 15,
94
- },
95
- });
@@ -1,35 +0,0 @@
1
- import React from 'react';
2
- import { View, StyleSheet, TouchableOpacity } from 'react-native';
3
- import { AtomicIcon, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
4
-
5
- interface DetailHeaderProps {
6
- readonly onClose: () => void;
7
- }
8
-
9
- export const DetailHeader: React.FC<DetailHeaderProps> = ({ onClose }) => {
10
- const tokens = useAppDesignTokens();
11
- const styles = useStyles(tokens);
12
-
13
- return (
14
- <View style={styles.headerContainer}>
15
- <TouchableOpacity style={styles.closeButton} onPress={onClose}>
16
- <AtomicIcon name="arrow-back" size="md" color="onSurface" />
17
- </TouchableOpacity>
18
- </View>
19
- );
20
- };
21
-
22
- const useStyles = (tokens: DesignTokens) => StyleSheet.create({
23
- headerContainer: {
24
- paddingVertical: tokens.spacing.xs,
25
- paddingHorizontal: tokens.spacing.sm,
26
- backgroundColor: tokens.colors.background,
27
- },
28
- closeButton: {
29
- padding: tokens.spacing.xs,
30
- width: 40,
31
- height: 40,
32
- alignItems: 'center',
33
- justifyContent: 'center',
34
- },
35
- });
@@ -1,53 +0,0 @@
1
- import React from 'react';
2
- import { View, StyleSheet, useWindowDimensions, TouchableOpacity } from 'react-native';
3
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
4
- import { Image } from 'expo-image';
5
-
6
- interface DetailImageProps {
7
- readonly uri: string;
8
- readonly onPress?: () => void;
9
- }
10
-
11
- const HORIZONTAL_PADDING = 16;
12
- const ASPECT_RATIO = 16 / 9;
13
-
14
- export const DetailImage: React.FC<DetailImageProps> = ({ uri, onPress }) => {
15
- const tokens = useAppDesignTokens();
16
- const { width } = useWindowDimensions();
17
- const imageWidth = width - (HORIZONTAL_PADDING * 2);
18
- const imageHeight = imageWidth / ASPECT_RATIO;
19
-
20
- const content = (
21
- <View style={[styles.frame, { width: imageWidth, height: imageHeight, backgroundColor: tokens.colors.surface }]}>
22
- <Image source={{ uri }} style={styles.image} contentFit="cover" />
23
- </View>
24
- );
25
-
26
- return (
27
- <View style={styles.container}>
28
- {onPress ? (
29
- <TouchableOpacity activeOpacity={0.9} onPress={onPress}>
30
- {content}
31
- </TouchableOpacity>
32
- ) : (
33
- content
34
- )}
35
- </View>
36
- );
37
- };
38
-
39
- const styles = StyleSheet.create({
40
- container: {
41
- paddingHorizontal: HORIZONTAL_PADDING,
42
- marginTop: 8,
43
- marginBottom: 12,
44
- },
45
- frame: {
46
- borderRadius: 16,
47
- overflow: 'hidden',
48
- },
49
- image: {
50
- width: '100%',
51
- height: '100%',
52
- },
53
- });
@@ -1,51 +0,0 @@
1
- import React from 'react';
2
- import { View, StyleSheet } from 'react-native';
3
- import { AtomicText, AtomicIcon, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
4
-
5
- interface DetailInfoProps {
6
- readonly title: string;
7
- readonly date: string;
8
- }
9
-
10
- export const DetailInfo: React.FC<DetailInfoProps> = ({ title, date }) => {
11
- const tokens = useAppDesignTokens();
12
- const styles = useStyles(tokens);
13
-
14
- return (
15
- <View style={styles.container}>
16
- <AtomicText style={styles.title}>{title}</AtomicText>
17
- <View style={styles.dateBadge}>
18
- <AtomicIcon name="calendar-outline" size="sm" color="primary" />
19
- <AtomicText style={styles.dateText}>{date}</AtomicText>
20
- </View>
21
- </View>
22
- );
23
- };
24
-
25
- const useStyles = (tokens: DesignTokens) => StyleSheet.create({
26
- container: {
27
- paddingHorizontal: tokens.spacing.md,
28
- paddingVertical: tokens.spacing.sm,
29
- gap: tokens.spacing.xs,
30
- },
31
- title: {
32
- fontSize: 18,
33
- fontWeight: '700',
34
- color: tokens.colors.textPrimary,
35
- },
36
- dateBadge: {
37
- flexDirection: 'row',
38
- alignItems: 'center',
39
- gap: 6,
40
- alignSelf: 'flex-start',
41
- paddingHorizontal: 12,
42
- paddingVertical: 6,
43
- borderRadius: 12,
44
- backgroundColor: tokens.colors.primary + '15',
45
- },
46
- dateText: {
47
- fontSize: 13,
48
- fontWeight: '600',
49
- color: tokens.colors.primary,
50
- },
51
- });
@@ -1,64 +0,0 @@
1
-
2
- import React from 'react';
3
- import { View, StyleSheet } from 'react-native';
4
- import { AtomicText, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
5
-
6
- interface DetailStoryProps {
7
- readonly story: string;
8
- }
9
-
10
- export const DetailStory: React.FC<DetailStoryProps> = ({ story }) => {
11
- const tokens = useAppDesignTokens();
12
- const styles = useStyles(tokens);
13
-
14
- if (!story) return null;
15
-
16
- return (
17
- <View style={styles.container}>
18
- <View style={styles.storyContainer}>
19
- <AtomicText style={styles.quoteMark}>&quot;</AtomicText>
20
- <AtomicText style={styles.text}>{story}</AtomicText>
21
- <View style={styles.quoteEndRow}>
22
- <AtomicText style={[styles.quoteMark, styles.quoteEnd]}>&quot;</AtomicText>
23
- </View>
24
- </View>
25
- </View>
26
- );
27
- };
28
-
29
- const useStyles = (tokens: DesignTokens) => StyleSheet.create({
30
- container: {
31
- paddingHorizontal: tokens.spacing.lg,
32
- marginBottom: tokens.spacing.lg,
33
- },
34
- storyContainer: {
35
- padding: tokens.spacing.lg,
36
- borderRadius: 20,
37
- borderWidth: 1,
38
- borderColor: tokens.colors.border,
39
- backgroundColor: tokens.colors.surface,
40
- },
41
- quoteMark: {
42
- fontSize: 48,
43
- lineHeight: 48,
44
- color: tokens.colors.primary,
45
- opacity: 0.4,
46
- marginBottom: -16,
47
- },
48
- quoteEndRow: {
49
- alignItems: 'flex-end',
50
- marginTop: -16,
51
- },
52
- quoteEnd: {
53
- marginBottom: 0,
54
- },
55
- text: {
56
- fontSize: 16,
57
- lineHeight: 26,
58
- textAlign: 'center',
59
- fontStyle: 'italic',
60
- fontWeight: '500',
61
- color: tokens.colors.textPrimary,
62
- paddingBottom: 4,
63
- },
64
- });
@@ -1,48 +0,0 @@
1
- /**
2
- * DetailVideo Component
3
- * Video player with thumbnail and play controls for creation detail view
4
- */
5
-
6
- import React, { useMemo } from "react";
7
- import { View } from "react-native";
8
- import { VideoView, useVideoPlayer } from "expo-video";
9
- import { useResponsive } from "@umituz/react-native-design-system";
10
-
11
- interface DetailVideoProps {
12
- readonly videoUrl: string;
13
- readonly _thumbnailUrl?: string;
14
- }
15
-
16
- export const DetailVideo: React.FC<DetailVideoProps> = ({
17
- videoUrl,
18
- _thumbnailUrl,
19
- }) => {
20
- const { width, horizontalPadding, spacingMultiplier } = useResponsive();
21
- const videoWidth = width - (horizontalPadding * 2);
22
-
23
- const player = useVideoPlayer(videoUrl, (player) => {
24
- player.loop = true;
25
- });
26
-
27
- const containerStyle = useMemo(() => ({
28
- paddingHorizontal: horizontalPadding,
29
- marginVertical: 16 * spacingMultiplier,
30
- }), [horizontalPadding, spacingMultiplier]);
31
-
32
- const videoStyle = useMemo(() => ({
33
- width: videoWidth,
34
- height: (videoWidth * 9) / 16, // 16:9 aspect ratio
35
- }), [videoWidth]);
36
-
37
- return (
38
- <View style={containerStyle}>
39
- <VideoView
40
- style={videoStyle}
41
- player={player}
42
- allowsFullscreen
43
- allowsPictureInPicture
44
- nativeControls
45
- />
46
- </View>
47
- );
48
- };
@@ -1,5 +0,0 @@
1
- export * from './DetailHeader';
2
- export * from './DetailImage';
3
- export * from './DetailVideo';
4
- export * from './DetailStory';
5
- export * from './DetailActions';
@@ -1,111 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { } from 'react-native';
3
- import { useAppDesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
4
- import type { Creation } from '../../domain/entities/Creation';
5
- import type { CreationsConfig } from '../../domain/value-objects/CreationsConfig';
6
- import type { ICreationsRepository } from '../../domain/repositories/ICreationsRepository';
7
- import { hasVideoContent, getPreviewUrl } from '../../domain/utils';
8
- import { useCreationRating } from '../hooks/useCreationRating';
9
- import { DetailHeader } from '../components/CreationDetail/DetailHeader';
10
- import { DetailInfo } from '../components/CreationDetail/DetailInfo';
11
- import { DetailImage } from '../components/CreationDetail/DetailImage';
12
- import { DetailVideo } from '../components/CreationDetail/DetailVideo';
13
- import { DetailStory } from '../components/CreationDetail/DetailStory';
14
- import { DetailActions } from '../components/CreationDetail/DetailActions';
15
- import { CreationRating } from '../components/CreationRating';
16
- import { getLocalizedTitle } from '../utils/filterUtils';
17
-
18
- /** Video creation types */
19
- const VIDEO_TYPES = ['text-to-video', 'image-to-video'] as const;
20
-
21
- interface CreationDetailScreenProps {
22
- readonly userId: string | null;
23
- readonly repository: ICreationsRepository;
24
- readonly creation: Creation;
25
- readonly config: CreationsConfig;
26
- readonly onClose: () => void;
27
- readonly onShare: (creation: Creation) => void;
28
- readonly onDelete: (creation: Creation) => void;
29
- readonly onViewResult?: (creation: Creation) => void;
30
- readonly t: (key: string) => string;
31
- }
32
-
33
- interface CreationMetadata {
34
- readonly names?: string;
35
- readonly story?: string;
36
- readonly description?: string;
37
- readonly date?: string;
38
- }
39
-
40
- export const CreationDetailScreen: React.FC<CreationDetailScreenProps> = ({
41
- userId,
42
- repository,
43
- creation,
44
- config,
45
- onClose,
46
- onShare,
47
- onDelete,
48
- onViewResult,
49
- t
50
- }) => {
51
- const tokens = useAppDesignTokens();
52
- const rateMutation = useCreationRating({ userId, repository });
53
-
54
- const handleRate = async (rating: number) => {
55
- await rateMutation.mutateAsync({ id: creation.id, rating });
56
- };
57
-
58
- // Extract data safely
59
- const metadata = (creation.metadata || {}) as CreationMetadata;
60
-
61
- // Resolve title:
62
- // 1. Manually set names in metadata
63
- // 2. Localized title from config types mapping
64
- // 3. Fallback to raw creation type (formatted)
65
- const title = metadata.names || getLocalizedTitle(config, t, creation.type);
66
- const story = metadata.story || metadata.description || "";
67
- const date = metadata.date || new Date(creation.createdAt).toLocaleDateString();
68
-
69
- // Detect if this is a video creation
70
- const isVideo = useMemo(() => {
71
- if (VIDEO_TYPES.includes(creation.type as typeof VIDEO_TYPES[number])) return true;
72
- if (hasVideoContent(creation.output)) return true;
73
- return false;
74
- }, [creation.type, creation.output]);
75
-
76
- // Get video URL and thumbnail for video content
77
- const videoUrl = creation.output?.videoUrl || creation.uri;
78
- const thumbnailUrl = getPreviewUrl(creation.output) || undefined;
79
-
80
- return (
81
- <ScreenLayout
82
- header={<DetailHeader onClose={onClose} />}
83
- >
84
- {isVideo ? (
85
- <DetailVideo videoUrl={videoUrl} _thumbnailUrl={thumbnailUrl} />
86
- ) : (
87
- <DetailImage uri={creation.uri} />
88
- )}
89
-
90
- <DetailInfo title={title} date={date} />
91
-
92
- <CreationRating
93
- rating={creation.rating || 0}
94
- onRate={handleRate}
95
- />
96
-
97
- {story ? (
98
- <DetailStory story={story} />
99
- ) : null}
100
-
101
- <DetailActions
102
- onShare={() => onShare(creation)}
103
- onDelete={() => onDelete(creation)}
104
- onViewResult={onViewResult ? () => onViewResult(creation) : undefined}
105
- shareLabel={t("result.shareButton")}
106
- deleteLabel={t("common.delete")}
107
- viewResultLabel={onViewResult ? t("result.viewResult") : undefined}
108
- />
109
- </ScreenLayout>
110
- );
111
- };