@umituz/react-native-ai-generation-content 1.17.18 → 1.17.20

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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/features/image-to-video/domain/constants/animation.constants.ts +47 -0
  3. package/src/features/image-to-video/domain/constants/duration.constants.ts +13 -0
  4. package/src/features/image-to-video/domain/constants/form.constants.ts +22 -0
  5. package/src/features/image-to-video/domain/constants/index.ts +23 -0
  6. package/src/features/image-to-video/domain/constants/music.constants.ts +53 -0
  7. package/src/features/image-to-video/domain/index.ts +4 -0
  8. package/src/features/image-to-video/domain/types/animation.types.ts +20 -0
  9. package/src/features/image-to-video/domain/types/config.types.ts +56 -0
  10. package/src/features/image-to-video/domain/types/duration.types.ts +11 -0
  11. package/src/features/image-to-video/domain/types/form.types.ts +35 -0
  12. package/src/features/image-to-video/domain/types/image-to-video.types.ts +18 -0
  13. package/src/features/image-to-video/domain/types/index.ts +25 -0
  14. package/src/features/image-to-video/domain/types/music.types.ts +21 -0
  15. package/src/features/text-to-image/domain/types/config.types.ts +9 -5
  16. package/src/features/text-to-image/domain/types/index.ts +4 -4
  17. package/src/features/text-to-image/index.ts +4 -4
  18. package/src/features/text-to-image/presentation/hooks/useGeneration.ts +5 -5
  19. package/src/features/text-to-voice/index.ts +34 -6
  20. package/src/features/text-to-voice/infrastructure/services/index.ts +0 -1
  21. package/src/features/text-to-voice/presentation/components/TextToVoiceAudioPlayer.tsx +81 -0
  22. package/src/features/text-to-voice/presentation/components/TextToVoiceErrorMessage.tsx +57 -0
  23. package/src/features/text-to-voice/presentation/components/TextToVoiceExamplePrompts.tsx +77 -0
  24. package/src/features/text-to-voice/presentation/components/TextToVoiceGenerateButton.tsx +87 -0
  25. package/src/features/text-to-voice/presentation/components/TextToVoiceHeader.tsx +73 -0
  26. package/src/features/text-to-voice/presentation/components/TextToVoiceOptionalInput.tsx +73 -0
  27. package/src/features/text-to-voice/presentation/components/TextToVoiceTextInput.tsx +85 -0
  28. package/src/features/text-to-voice/presentation/components/index.ts +7 -0
  29. package/src/features/text-to-voice/presentation/hooks/index.ts +5 -4
  30. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceForm.ts +91 -0
  31. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceGeneration.ts +116 -0
  32. package/src/features/text-to-voice/presentation/index.ts +1 -0
  33. package/src/features/text-to-voice/presentation/hooks/useTextToVoiceFeature.ts +0 -105
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.18",
3
+ "version": "1.17.20",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Animation Style Constants
3
+ * Default animation styles for image-to-video
4
+ */
5
+
6
+ import type { AnimationStyle } from "../types";
7
+
8
+ export const DEFAULT_ANIMATION_STYLES: AnimationStyle[] = [
9
+ {
10
+ id: "ken_burns",
11
+ name: "Ken Burns",
12
+ description: "Slow zoom and pan effect",
13
+ icon: "Maximize2",
14
+ },
15
+ {
16
+ id: "zoom_in",
17
+ name: "Zoom In",
18
+ description: "Gradual zoom in effect",
19
+ icon: "ZoomIn",
20
+ },
21
+ {
22
+ id: "zoom_out",
23
+ name: "Zoom Out",
24
+ description: "Gradual zoom out effect",
25
+ icon: "ZoomOut",
26
+ },
27
+ {
28
+ id: "slide_left",
29
+ name: "Slide Left",
30
+ description: "Pan from right to left",
31
+ icon: "ArrowLeft",
32
+ },
33
+ {
34
+ id: "slide_right",
35
+ name: "Slide Right",
36
+ description: "Pan from left to right",
37
+ icon: "ArrowRight",
38
+ },
39
+ {
40
+ id: "parallax",
41
+ name: "Parallax",
42
+ description: "3D depth effect",
43
+ icon: "Layers",
44
+ },
45
+ ];
46
+
47
+ export const DEFAULT_ANIMATION_STYLE_ID = "ken_burns";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Duration Constants
3
+ * Default duration options for image-to-video
4
+ */
5
+
6
+ import type { VideoDuration, DurationOption } from "../types";
7
+
8
+ export const DEFAULT_DURATION_OPTIONS: DurationOption[] = [
9
+ { value: 4, label: "4s" },
10
+ { value: 8, label: "8s" },
11
+ ];
12
+
13
+ export const DEFAULT_VIDEO_DURATION: VideoDuration = 4;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Form Constants
3
+ * Default form values for image-to-video
4
+ */
5
+
6
+ import type { ImageToVideoFormDefaults, ImageToVideoFormConfig } from "../types";
7
+ import { DEFAULT_ANIMATION_STYLE_ID } from "./animation.constants";
8
+ import { DEFAULT_MUSIC_MOOD_ID } from "./music.constants";
9
+ import { DEFAULT_VIDEO_DURATION } from "./duration.constants";
10
+
11
+ export const DEFAULT_FORM_VALUES: ImageToVideoFormDefaults = {
12
+ animationStyle: DEFAULT_ANIMATION_STYLE_ID,
13
+ duration: DEFAULT_VIDEO_DURATION,
14
+ musicMood: DEFAULT_MUSIC_MOOD_ID,
15
+ };
16
+
17
+ export const DEFAULT_FORM_CONFIG: ImageToVideoFormConfig = {
18
+ maxImages: 10,
19
+ creditCost: 1,
20
+ enableCustomAudio: true,
21
+ enableMotionPrompt: false,
22
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Image-to-Video Constants Index
3
+ */
4
+
5
+ export {
6
+ DEFAULT_ANIMATION_STYLES,
7
+ DEFAULT_ANIMATION_STYLE_ID,
8
+ } from "./animation.constants";
9
+
10
+ export {
11
+ DEFAULT_MUSIC_MOODS,
12
+ DEFAULT_MUSIC_MOOD_ID,
13
+ } from "./music.constants";
14
+
15
+ export {
16
+ DEFAULT_DURATION_OPTIONS,
17
+ DEFAULT_VIDEO_DURATION,
18
+ } from "./duration.constants";
19
+
20
+ export {
21
+ DEFAULT_FORM_VALUES,
22
+ DEFAULT_FORM_CONFIG,
23
+ } from "./form.constants";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Music Mood Constants
3
+ * Default music moods for image-to-video
4
+ */
5
+
6
+ import type { MusicMood } from "../types";
7
+
8
+ export const DEFAULT_MUSIC_MOODS: MusicMood[] = [
9
+ {
10
+ id: "none",
11
+ name: "No Music",
12
+ description: "Silent video",
13
+ icon: "volume-mute-outline",
14
+ },
15
+ {
16
+ id: "energetic",
17
+ name: "Energetic",
18
+ description: "Upbeat and dynamic",
19
+ icon: "flash-outline",
20
+ },
21
+ {
22
+ id: "calm",
23
+ name: "Calm",
24
+ description: "Peaceful and relaxing",
25
+ icon: "leaf-outline",
26
+ },
27
+ {
28
+ id: "happy",
29
+ name: "Happy",
30
+ description: "Cheerful and joyful",
31
+ icon: "happy-outline",
32
+ },
33
+ {
34
+ id: "emotional",
35
+ name: "Emotional",
36
+ description: "Touching and heartfelt",
37
+ icon: "heart-outline",
38
+ },
39
+ {
40
+ id: "rhythmic",
41
+ name: "Rhythmic",
42
+ description: "Beat-driven and groovy",
43
+ icon: "musical-notes-outline",
44
+ },
45
+ {
46
+ id: "custom",
47
+ name: "Custom",
48
+ description: "Upload your own audio",
49
+ icon: "cloud-upload-outline",
50
+ },
51
+ ];
52
+
53
+ export const DEFAULT_MUSIC_MOOD_ID = "none";
@@ -1 +1,5 @@
1
+ // Types
1
2
  export * from "./types";
3
+
4
+ // Constants
5
+ export * from "./constants";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Animation Types for Image-to-Video
3
+ * Defines animation style options
4
+ */
5
+
6
+ export interface AnimationStyle {
7
+ id: string;
8
+ name: string;
9
+ description: string;
10
+ icon: string;
11
+ }
12
+
13
+ export type AnimationStyleId =
14
+ | "ken_burns"
15
+ | "zoom_in"
16
+ | "zoom_out"
17
+ | "slide_left"
18
+ | "slide_right"
19
+ | "parallax"
20
+ | string;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Config Types for Image-to-Video
3
+ * Defines callbacks and configuration options
4
+ */
5
+
6
+ import type { ImageToVideoResult } from "./image-to-video.types";
7
+ import type { ImageToVideoFormState } from "./form.types";
8
+
9
+ export interface ImageToVideoCallbacks {
10
+ onGenerate: (formState: ImageToVideoFormState) => Promise<void>;
11
+ onSelectImages?: () => Promise<string[]>;
12
+ onSelectCustomAudio?: () => Promise<string | null>;
13
+ onCreditCheck?: (cost: number) => boolean;
14
+ onShowPaywall?: (cost: number) => void;
15
+ onSuccess?: (result: ImageToVideoResult) => void;
16
+ onError?: (error: string) => void;
17
+ }
18
+
19
+ export interface ImageToVideoFormConfig {
20
+ maxImages?: number;
21
+ creditCost?: number;
22
+ enableCustomAudio?: boolean;
23
+ enableMotionPrompt?: boolean;
24
+ }
25
+
26
+ export interface ImageToVideoTranslationsExtended {
27
+ sectionTitles: {
28
+ selectedImages: string;
29
+ animationStyle: string;
30
+ durationPerImage: string;
31
+ addMusic: string;
32
+ };
33
+ imageSelection: {
34
+ selectImages: string;
35
+ chooseUpTo: string;
36
+ addMore: string;
37
+ };
38
+ duration: {
39
+ totalVideo: string;
40
+ };
41
+ music: {
42
+ customAudioSelected: string;
43
+ };
44
+ hero: {
45
+ title: string;
46
+ subtitle: string;
47
+ };
48
+ generate: {
49
+ buttonText: string;
50
+ generatingText: string;
51
+ };
52
+ errors: {
53
+ noImages: string;
54
+ noImagesMessage: string;
55
+ };
56
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Duration Types for Image-to-Video
3
+ * Defines video duration options
4
+ */
5
+
6
+ export type VideoDuration = 4 | 8 | number;
7
+
8
+ export interface DurationOption {
9
+ value: VideoDuration;
10
+ label?: string;
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Form Types for Image-to-Video
3
+ * Defines form state and actions
4
+ */
5
+
6
+ import type { AnimationStyleId } from "./animation.types";
7
+ import type { MusicMoodId } from "./music.types";
8
+ import type { VideoDuration } from "./duration.types";
9
+
10
+ export interface ImageToVideoFormState {
11
+ selectedImages: string[];
12
+ animationStyle: AnimationStyleId;
13
+ duration: VideoDuration;
14
+ musicMood: MusicMoodId;
15
+ customAudioUri: string | null;
16
+ motionPrompt: string;
17
+ }
18
+
19
+ export interface ImageToVideoFormActions {
20
+ setSelectedImages: (images: string[]) => void;
21
+ addImages: (images: string[]) => void;
22
+ removeImage: (index: number) => void;
23
+ setAnimationStyle: (style: AnimationStyleId) => void;
24
+ setDuration: (duration: VideoDuration) => void;
25
+ setMusicMood: (mood: MusicMoodId) => void;
26
+ setCustomAudioUri: (uri: string | null) => void;
27
+ setMotionPrompt: (prompt: string) => void;
28
+ reset: () => void;
29
+ }
30
+
31
+ export interface ImageToVideoFormDefaults {
32
+ animationStyle?: AnimationStyleId;
33
+ duration?: VideoDuration;
34
+ musicMood?: MusicMoodId;
35
+ }
@@ -3,11 +3,17 @@
3
3
  * Request, Result, Config types for image-to-video generation
4
4
  */
5
5
 
6
+ import type { AnimationStyleId } from "./animation.types";
7
+ import type { MusicMoodId } from "./music.types";
8
+ import type { VideoDuration } from "./duration.types";
9
+
6
10
  export interface ImageToVideoOptions {
7
11
  duration?: number;
8
12
  motionStrength?: number;
9
13
  aspectRatio?: "16:9" | "9:16" | "1:1";
10
14
  fps?: number;
15
+ animationStyle?: AnimationStyleId;
16
+ musicMood?: MusicMoodId;
11
17
  }
12
18
 
13
19
  export interface ImageToVideoRequest {
@@ -16,6 +22,12 @@ export interface ImageToVideoRequest {
16
22
  userId: string;
17
23
  motionPrompt?: string;
18
24
  options?: ImageToVideoOptions;
25
+ allImages?: string[];
26
+ customAudioUri?: string | null;
27
+ animationStyle?: AnimationStyleId;
28
+ duration?: VideoDuration;
29
+ musicMood?: MusicMoodId;
30
+ model?: string;
19
31
  }
20
32
 
21
33
  export interface ImageToVideoResult {
@@ -26,6 +38,12 @@ export interface ImageToVideoResult {
26
38
  requestId?: string;
27
39
  }
28
40
 
41
+ export interface ImageToVideoGenerationState {
42
+ isGenerating: boolean;
43
+ progress: number;
44
+ error: string | null;
45
+ }
46
+
29
47
  export interface ImageToVideoFeatureState {
30
48
  imageUri: string | null;
31
49
  motionPrompt: string;
@@ -1,7 +1,32 @@
1
+ // Animation Types
2
+ export type { AnimationStyle, AnimationStyleId } from "./animation.types";
3
+
4
+ // Music Types
5
+ export type { MusicMood, MusicMoodId } from "./music.types";
6
+
7
+ // Duration Types
8
+ export type { VideoDuration, DurationOption } from "./duration.types";
9
+
10
+ // Form Types
11
+ export type {
12
+ ImageToVideoFormState,
13
+ ImageToVideoFormActions,
14
+ ImageToVideoFormDefaults,
15
+ } from "./form.types";
16
+
17
+ // Config Types
18
+ export type {
19
+ ImageToVideoCallbacks,
20
+ ImageToVideoFormConfig,
21
+ ImageToVideoTranslationsExtended,
22
+ } from "./config.types";
23
+
24
+ // Core Feature Types
1
25
  export type {
2
26
  ImageToVideoOptions,
3
27
  ImageToVideoRequest,
4
28
  ImageToVideoResult,
29
+ ImageToVideoGenerationState,
5
30
  ImageToVideoFeatureState,
6
31
  ImageToVideoTranslations,
7
32
  ImageToVideoInputBuilder,
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Music Types for Image-to-Video
3
+ * Defines music mood options
4
+ */
5
+
6
+ export interface MusicMood {
7
+ id: string;
8
+ name: string;
9
+ description: string;
10
+ icon: string;
11
+ }
12
+
13
+ export type MusicMoodId =
14
+ | "none"
15
+ | "energetic"
16
+ | "calm"
17
+ | "happy"
18
+ | "emotional"
19
+ | "rhythmic"
20
+ | "custom"
21
+ | string;
@@ -12,7 +12,7 @@ import type {
12
12
  TextToImageFormDefaults,
13
13
  } from "./form.types";
14
14
 
15
- export interface GenerationRequest {
15
+ export interface TextToImageGenerationRequest {
16
16
  prompt: string;
17
17
  model?: string;
18
18
  aspectRatio: AspectRatio;
@@ -24,20 +24,24 @@ export interface GenerationRequest {
24
24
  outputFormat?: OutputFormat;
25
25
  }
26
26
 
27
- export interface GenerationResultSuccess {
27
+ export interface TextToImageGenerationResultSuccess {
28
28
  success: true;
29
29
  imageUrls: string[];
30
30
  }
31
31
 
32
- export interface GenerationResultError {
32
+ export interface TextToImageGenerationResultError {
33
33
  success: false;
34
34
  error: string;
35
35
  }
36
36
 
37
- export type GenerationResult = GenerationResultSuccess | GenerationResultError;
37
+ export type TextToImageGenerationResult =
38
+ | TextToImageGenerationResultSuccess
39
+ | TextToImageGenerationResultError;
38
40
 
39
41
  export interface TextToImageCallbacks {
40
- executeGeneration: (request: GenerationRequest) => Promise<GenerationResult>;
42
+ executeGeneration: (
43
+ request: TextToImageGenerationRequest,
44
+ ) => Promise<TextToImageGenerationResult>;
41
45
  calculateCost: (numImages: NumImages, model?: string | null) => number;
42
46
  canAfford: (cost: number) => boolean;
43
47
  isAuthenticated: () => boolean;
@@ -17,10 +17,10 @@ export type {
17
17
 
18
18
  // Config types
19
19
  export type {
20
- GenerationRequest,
21
- GenerationResult,
22
- GenerationResultSuccess,
23
- GenerationResultError,
20
+ TextToImageGenerationRequest,
21
+ TextToImageGenerationResult,
22
+ TextToImageGenerationResultSuccess,
23
+ TextToImageGenerationResultError,
24
24
  TextToImageCallbacks,
25
25
  TextToImageFormConfig,
26
26
  TextToImageTranslations,
@@ -21,10 +21,10 @@ export type {
21
21
 
22
22
  // Config Types
23
23
  export type {
24
- GenerationRequest,
25
- GenerationResult,
26
- GenerationResultSuccess,
27
- GenerationResultError,
24
+ TextToImageGenerationRequest,
25
+ TextToImageGenerationResult,
26
+ TextToImageGenerationResultSuccess,
27
+ TextToImageGenerationResultError,
28
28
  TextToImageCallbacks,
29
29
  TextToImageFormConfig,
30
30
  TextToImageTranslations,
@@ -7,8 +7,8 @@ import { useState, useCallback } from "react";
7
7
  import type {
8
8
  TextToImageFormState,
9
9
  TextToImageCallbacks,
10
- GenerationResult,
11
- GenerationRequest,
10
+ TextToImageGenerationResult,
11
+ TextToImageGenerationRequest,
12
12
  } from "../../domain/types";
13
13
 
14
14
  export interface GenerationState {
@@ -26,7 +26,7 @@ export interface UseGenerationOptions {
26
26
  export interface UseGenerationReturn {
27
27
  generationState: GenerationState;
28
28
  totalCost: number;
29
- handleGenerate: () => Promise<GenerationResult | null>;
29
+ handleGenerate: () => Promise<TextToImageGenerationResult | null>;
30
30
  }
31
31
 
32
32
  const initialState: GenerationState = {
@@ -41,7 +41,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
41
41
 
42
42
  const totalCost = callbacks.calculateCost(formState.numImages, formState.selectedModel);
43
43
 
44
- const handleGenerate = useCallback(async (): Promise<GenerationResult | null> => {
44
+ const handleGenerate = useCallback(async (): Promise<TextToImageGenerationResult | null> => {
45
45
  const trimmedPrompt = formState.prompt.trim();
46
46
 
47
47
  if (!trimmedPrompt) {
@@ -61,7 +61,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
61
61
 
62
62
  setGenerationState({ isGenerating: true, progress: 0, error: null });
63
63
 
64
- const request: GenerationRequest = {
64
+ const request: TextToImageGenerationRequest = {
65
65
  prompt: trimmedPrompt,
66
66
  model: formState.selectedModel ?? undefined,
67
67
  aspectRatio: formState.aspectRatio,
@@ -8,20 +8,48 @@ export type {
8
8
  TextToVoiceOptions,
9
9
  TextToVoiceRequest,
10
10
  TextToVoiceResult,
11
- TextToVoiceFeatureState,
12
- TextToVoiceTranslations,
11
+ TextToVoiceGenerationState,
12
+ VoiceGeneration,
13
+ TextToVoiceFormState,
14
+ TextToVoiceFormSetters,
15
+ TextToVoiceFormReturn,
16
+ TextToVoiceFormConfig,
17
+ TextToVoiceTextInputProps,
18
+ TextToVoiceOptionalInputProps,
19
+ TextToVoiceExamplePromptsProps,
20
+ TextToVoiceGenerateButtonProps,
21
+ TextToVoiceAudioPlayerProps,
22
+ TextToVoiceErrorMessageProps,
23
+ TextToVoiceHeaderProps,
24
+ TextToVoiceScreenConfig,
25
+ TextToVoiceTranslationKeys,
13
26
  TextToVoiceInputBuilder,
14
27
  TextToVoiceResultExtractor,
15
28
  TextToVoiceFeatureConfig,
29
+ TextToVoiceExecuteOptions,
16
30
  } from "./domain";
17
31
 
18
32
  // Infrastructure Services
19
33
  export { executeTextToVoice, hasTextToVoiceSupport } from "./infrastructure";
20
- export type { ExecuteTextToVoiceOptions } from "./infrastructure";
21
34
 
22
35
  // Presentation Hooks
23
- export { useTextToVoiceFeature } from "./presentation";
36
+ export {
37
+ useTextToVoiceForm,
38
+ useTextToVoiceGeneration,
39
+ } from "./presentation";
40
+
24
41
  export type {
25
- UseTextToVoiceFeatureProps,
26
- UseTextToVoiceFeatureReturn,
42
+ UseTextToVoiceGenerationProps,
43
+ UseTextToVoiceGenerationReturn,
44
+ } from "./presentation";
45
+
46
+ // Presentation Components
47
+ export {
48
+ TextToVoiceTextInput,
49
+ TextToVoiceOptionalInput,
50
+ TextToVoiceExamplePrompts,
51
+ TextToVoiceGenerateButton,
52
+ TextToVoiceAudioPlayer,
53
+ TextToVoiceErrorMessage,
54
+ TextToVoiceHeader,
27
55
  } from "./presentation";
@@ -2,4 +2,3 @@ export {
2
2
  executeTextToVoice,
3
3
  hasTextToVoiceSupport,
4
4
  } from "./text-to-voice-executor";
5
- export type { ExecuteTextToVoiceOptions } from "./text-to-voice-executor";
@@ -0,0 +1,81 @@
1
+ /**
2
+ * TextToVoiceAudioPlayer Component
3
+ * Audio player with play button
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import { useLocalization } from "@umituz/react-native-localization";
14
+ import type { TextToVoiceAudioPlayerProps } from "../../domain/types";
15
+
16
+ export const TextToVoiceAudioPlayer: React.FC<TextToVoiceAudioPlayerProps> = ({
17
+ onPlay,
18
+ successTextKey,
19
+ playButtonTextKey,
20
+ style,
21
+ }) => {
22
+ const { t } = useLocalization();
23
+ const tokens = useAppDesignTokens();
24
+
25
+ return (
26
+ <View
27
+ style={[styles.card, { backgroundColor: tokens.colors.surface }, style]}
28
+ >
29
+ <View style={styles.header}>
30
+ <AtomicIcon name="checkmark-circle" size="md" color="success" />
31
+ <AtomicText
32
+ type="bodyMedium"
33
+ style={[styles.successText, { color: tokens.colors.success }]}
34
+ >
35
+ {t(successTextKey)}
36
+ </AtomicText>
37
+ </View>
38
+ <TouchableOpacity
39
+ style={[styles.playButton, { backgroundColor: tokens.colors.primary }]}
40
+ onPress={onPlay}
41
+ >
42
+ <AtomicIcon name="play" size="md" color="onPrimary" />
43
+ <AtomicText
44
+ type="bodyMedium"
45
+ style={[styles.buttonText, { color: tokens.colors.onPrimary }]}
46
+ >
47
+ {t(playButtonTextKey)}
48
+ </AtomicText>
49
+ </TouchableOpacity>
50
+ </View>
51
+ );
52
+ };
53
+
54
+ const styles = StyleSheet.create({
55
+ card: {
56
+ margin: 16,
57
+ marginTop: 0,
58
+ padding: 16,
59
+ borderRadius: 12,
60
+ },
61
+ header: {
62
+ flexDirection: "row",
63
+ alignItems: "center",
64
+ marginBottom: 12,
65
+ },
66
+ successText: {
67
+ fontWeight: "600",
68
+ marginLeft: 8,
69
+ },
70
+ playButton: {
71
+ flexDirection: "row",
72
+ alignItems: "center",
73
+ justifyContent: "center",
74
+ padding: 12,
75
+ borderRadius: 8,
76
+ },
77
+ buttonText: {
78
+ fontWeight: "600",
79
+ marginLeft: 8,
80
+ },
81
+ });