@umituz/react-native-mascot 1.0.3 → 1.0.7

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 (43) hide show
  1. package/README.md +60 -0
  2. package/package.json +2 -1
  3. package/src/application/dto/MascotDTO.ts +64 -0
  4. package/src/application/errors/MascotErrors.ts +76 -0
  5. package/src/application/services/AnimationStateManager.ts +69 -0
  6. package/src/application/services/AppearanceManagement.ts +40 -0
  7. package/src/application/services/MascotService.ts +203 -0
  8. package/src/application/services/PersonalityManagement.ts +39 -0
  9. package/src/application/services/StateHistory.ts +55 -0
  10. package/src/application/services/StateMachine.ts +154 -0
  11. package/src/application/services/StateTransitions.ts +73 -0
  12. package/src/application.ts +40 -0
  13. package/src/assets/index.ts +14 -19
  14. package/src/core.ts +62 -0
  15. package/src/domain/entities/Mascot.ts +197 -99
  16. package/src/domain/types/AnimationStateTypes.ts +148 -0
  17. package/src/domain/types/MascotTypes.ts +9 -0
  18. package/src/domain/value-objects/AnimationState.ts +126 -0
  19. package/src/domain/value-objects/EnergyLevel.ts +80 -0
  20. package/src/domain/value-objects/FriendlinessLevel.ts +66 -0
  21. package/src/domain/value-objects/Mood.ts +59 -0
  22. package/src/domain/value-objects/PlayfulnessLevel.ts +66 -0
  23. package/src/index.ts +16 -68
  24. package/src/infrastructure/controllers/AnimationController.ts +26 -122
  25. package/src/infrastructure/controllers/AnimationPlayer.ts +104 -0
  26. package/src/infrastructure/controllers/AnimationTimer.ts +62 -0
  27. package/src/infrastructure/controllers/EventManager.ts +108 -0
  28. package/src/infrastructure/di/Container.ts +153 -0
  29. package/src/infrastructure/managers/AssetManager.ts +134 -63
  30. package/src/infrastructure/managers/MascotBuilder.ts +89 -0
  31. package/src/infrastructure/managers/MascotFactory.ts +24 -176
  32. package/src/infrastructure/managers/MascotTemplates.ts +151 -0
  33. package/src/infrastructure/utils/LRUCache.ts +218 -0
  34. package/src/infrastructure.ts +24 -0
  35. package/src/presentation/components/LottieMascot.tsx +85 -0
  36. package/src/presentation/components/MascotView.tsx +42 -233
  37. package/src/presentation/components/SVGMascot.tsx +61 -0
  38. package/src/presentation/contexts/MascotContext.tsx +28 -111
  39. package/src/presentation/hooks/useMascot.ts +153 -169
  40. package/src/presentation/hooks/useMascotAnimation.ts +48 -94
  41. package/src/presentation/hooks/useMascotState.ts +213 -0
  42. package/src/presentation.ts +37 -0
  43. package/src/types.d.ts +4 -0
@@ -1,22 +1,23 @@
1
1
  /**
2
2
  * useMascot Hook
3
- * Main hook for mascot management
3
+ * Thin wrapper that delegates to MascotService
4
4
  */
5
5
 
6
6
  import { useCallback, useEffect, useState, useRef } from 'react';
7
- import { Mascot } from '../../domain/entities/Mascot';
7
+ import type { Mascot } from '../../domain/entities/Mascot';
8
8
  import type {
9
9
  MascotConfig,
10
10
  MascotMood,
11
11
  MascotAppearance,
12
12
  } from '../../domain/types/MascotTypes';
13
- import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
14
- import { AnimationController } from '../../infrastructure/controllers/AnimationController';
15
13
  import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
14
+ import type { MascotService } from '../../application/services/MascotService';
15
+ import { DIContainer } from '../../infrastructure/di/Container';
16
+ import type { MascotTemplate } from '../../application/services/MascotService';
16
17
 
17
18
  export interface UseMascotOptions {
18
19
  config?: MascotConfig;
19
- template?: string;
20
+ template?: MascotTemplate;
20
21
  autoInitialize?: boolean;
21
22
  }
22
23
 
@@ -25,12 +26,18 @@ export interface UseMascotReturn {
25
26
  isReady: boolean;
26
27
  isPlaying: boolean;
27
28
  currentAnimation: string | null;
28
- initialize: (config: MascotConfig) => void;
29
- initializeFromTemplate: (template: string, customizations?: Partial<MascotConfig>) => void;
29
+ initialize: (config: MascotConfig) => Promise<void>;
30
+ fromTemplate: (template: MascotTemplate, customizations?: Partial<MascotConfig>) => Promise<void>;
30
31
  setMood: (mood: MascotMood) => void;
31
32
  setEnergy: (energy: number) => void;
33
+ setFriendliness: (friendliness: number) => void;
34
+ setPlayfulness: (playfulness: number) => void;
35
+ cheerUp: () => void;
36
+ boostEnergy: (amount: number) => void;
32
37
  playAnimation: (animationId: string, options?: AnimationOptions) => Promise<void>;
33
38
  stopAnimation: () => void;
39
+ pauseAnimation: () => void;
40
+ resumeAnimation: () => void;
34
41
  updateAppearance: (appearance: Partial<MascotAppearance>) => void;
35
42
  setBaseColor: (color: string) => void;
36
43
  setAccentColor: (color: string) => void;
@@ -46,179 +53,156 @@ export interface UseMascotReturn {
46
53
  }
47
54
 
48
55
  export function useMascot(options: UseMascotOptions = {}): UseMascotReturn {
49
- const {
50
- config: initialConfig,
51
- template: initialTemplate,
52
- autoInitialize = true,
53
- } = options;
56
+ const { config: initialConfig, template: initialTemplate, autoInitialize = true } = options;
54
57
 
55
- const [mascot, setMascot] = useState<Mascot | null>(null);
56
- const [isReady, setIsReady] = useState(false);
57
- const [isPlaying, setIsPlaying] = useState(false);
58
- const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
58
+ const [state, setState] = useState(() => ({
59
+ mascot: null as Mascot | null,
60
+ isReady: false,
61
+ isPlaying: false,
62
+ currentAnimation: null as string | null,
63
+ }));
59
64
 
60
- const animationControllerRef = useRef<AnimationController | null>(null);
65
+ const serviceRef = useRef<MascotService | null>(null);
61
66
 
62
- // Initialize mascot
63
- const initialize = useCallback((config: MascotConfig) => {
64
- const newMascot = new Mascot(config);
65
- setMascot(newMascot);
66
- setIsReady(true);
67
- if (!animationControllerRef.current) {
68
- animationControllerRef.current = new AnimationController();
69
- }
70
- }, []);
71
-
72
- const initializeFromTemplate = useCallback((
73
- template: string,
74
- customizations?: Partial<MascotConfig>
75
- ) => {
76
- const newMascot = MascotFactory.createFromTemplate(
77
- template as 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero',
78
- customizations
79
- );
80
- setMascot(newMascot);
81
- setIsReady(true);
82
- if (!animationControllerRef.current) {
83
- animationControllerRef.current = new AnimationController();
84
- }
85
- }, []);
86
-
87
- // Mood management
88
- const setMood = useCallback((mood: MascotMood) => {
89
- setMascot((prev) => {
90
- if (!prev) return null;
91
- prev.setMood(mood);
92
- return prev.clone();
93
- });
94
- }, []);
95
-
96
- const setEnergy = useCallback((energy: number) => {
97
- setMascot((prev) => {
98
- if (!prev) return null;
99
- prev.setEnergy(energy);
100
- return prev.clone();
101
- });
102
- }, []);
103
-
104
- // Animation management
105
- const playAnimation = useCallback(async (animationId: string, options?: AnimationOptions) => {
106
- if (!mascot || !animationControllerRef.current) return;
107
-
108
- const animation = mascot.getAnimation(animationId);
109
- if (!animation) {
110
- console.warn(`Animation ${animationId} not found`);
111
- return;
112
- }
113
-
114
- setIsPlaying(true);
115
- setCurrentAnimation(animationId);
116
-
117
- if (animationControllerRef.current) {
118
- await animationControllerRef.current.play(animation, options);
119
- }
120
-
121
- setIsPlaying(false);
122
- setCurrentAnimation(null);
123
- }, [mascot]);
124
-
125
- const stopAnimation = useCallback(() => {
126
- if (animationControllerRef.current) {
127
- animationControllerRef.current.stop();
67
+ // Initialize service once and subscribe to changes
68
+ useEffect(() => {
69
+ if (serviceRef.current == null) {
70
+ const container = DIContainer.getInstance();
71
+ const service = container.getMascotService();
72
+ serviceRef.current = service;
73
+
74
+ // Subscribe to service changes
75
+ const unsubscribe = service.subscribe(() => {
76
+ setState({
77
+ mascot: service.mascot,
78
+ isReady: service.isReady,
79
+ isPlaying: service.isPlaying,
80
+ currentAnimation: service.currentAnimation,
81
+ });
82
+ });
83
+
84
+ return unsubscribe;
128
85
  }
129
- setIsPlaying(false);
130
- setCurrentAnimation(null);
131
- }, []);
132
-
133
- // Appearance management
134
- const updateAppearance = useCallback((appearance: Partial<MascotAppearance>) => {
135
- setMascot((prev) => {
136
- if (!prev) return null;
137
- prev.updateAppearance(appearance);
138
- return prev.clone();
139
- });
140
- }, []);
141
-
142
- const setBaseColor = useCallback((color: string) => {
143
- setMascot((prev) => {
144
- if (!prev) return null;
145
- prev.setBaseColor(color);
146
- return prev.clone();
147
- });
148
- }, []);
149
-
150
- const setAccentColor = useCallback((color: string) => {
151
- setMascot((prev) => {
152
- if (!prev) return null;
153
- prev.setAccentColor(color);
154
- return prev.clone();
155
- });
156
- }, []);
157
-
158
- const addAccessory = useCallback((accessory: {
159
- id: string;
160
- type: string;
161
- color?: string;
162
- position?: { x: number; y: number };
163
- }) => {
164
- setMascot((prev) => {
165
- if (!prev) return null;
166
- prev.addAccessory(accessory);
167
- return prev.clone();
168
- });
169
- }, []);
170
-
171
- const removeAccessory = useCallback((accessoryId: string) => {
172
- setMascot((prev) => {
173
- if (!prev) return null;
174
- prev.removeAccessory(accessoryId);
175
- return prev.clone();
176
- });
177
- }, []);
178
-
179
- // Visibility and position
180
- const setVisible = useCallback((visible: boolean) => {
181
- setMascot((prev) => {
182
- if (!prev) return null;
183
- prev.setVisible(visible);
184
- return prev.clone();
185
- });
186
- }, []);
187
-
188
- const setPosition = useCallback((position: { x: number; y: number }) => {
189
- setMascot((prev) => {
190
- if (!prev) return null;
191
- prev.setPosition(position);
192
- return prev.clone();
193
- });
86
+ return undefined;
194
87
  }, []);
195
88
 
196
- // Auto-initialize
89
+ // Auto-initialize
197
90
  useEffect(() => {
91
+ const service = serviceRef.current;
92
+ if (!service) return;
198
93
  if (autoInitialize && initialConfig) {
199
- initialize(initialConfig);
94
+ service.initialize(initialConfig);
200
95
  } else if (autoInitialize && initialTemplate) {
201
- initializeFromTemplate(initialTemplate);
96
+ service.fromTemplate(initialTemplate);
202
97
  }
203
- }, [autoInitialize, initialConfig, initialTemplate, initialize, initializeFromTemplate]);
98
+ }, [autoInitialize, initialConfig, initialTemplate]);
204
99
 
100
+ // ✅ All methods delegate to service - NO business logic!
205
101
  return {
206
- mascot,
207
- isReady,
208
- isPlaying,
209
- currentAnimation,
210
- initialize,
211
- initializeFromTemplate,
212
- setMood,
213
- setEnergy,
214
- playAnimation,
215
- stopAnimation,
216
- updateAppearance,
217
- setBaseColor,
218
- setAccentColor,
219
- addAccessory,
220
- removeAccessory,
221
- setVisible,
222
- setPosition,
102
+ ...state,
103
+ initialize: useCallback((config: MascotConfig) => {
104
+ const service = serviceRef.current;
105
+ if (!service) throw new Error('Service not initialized');
106
+ return service.initialize(config);
107
+ }, []),
108
+ fromTemplate: useCallback(
109
+ (template: MascotTemplate, customizations?: Partial<MascotConfig>) => {
110
+ const service = serviceRef.current;
111
+ if (!service) throw new Error('Service not initialized');
112
+ return service.fromTemplate(template, customizations);
113
+ },
114
+ []
115
+ ),
116
+ setMood: useCallback((mood: MascotMood) => {
117
+ const service = serviceRef.current;
118
+ if (!service) throw new Error('Service not initialized');
119
+ service.setMood(mood);
120
+ }, []),
121
+ setEnergy: useCallback((energy: number) => {
122
+ const service = serviceRef.current;
123
+ if (!service) throw new Error('Service not initialized');
124
+ service.setEnergy(energy);
125
+ }, []),
126
+ setFriendliness: useCallback((friendliness: number) => {
127
+ const service = serviceRef.current;
128
+ if (!service) throw new Error('Service not initialized');
129
+ service.setFriendliness(friendliness);
130
+ }, []),
131
+ setPlayfulness: useCallback((playfulness: number) => {
132
+ const service = serviceRef.current;
133
+ if (!service) throw new Error('Service not initialized');
134
+ service.setPlayfulness(playfulness);
135
+ }, []),
136
+ cheerUp: useCallback(() => {
137
+ const service = serviceRef.current;
138
+ if (!service) throw new Error('Service not initialized');
139
+ service.cheerUp();
140
+ }, []),
141
+ boostEnergy: useCallback((amount: number) => {
142
+ const service = serviceRef.current;
143
+ if (!service) throw new Error('Service not initialized');
144
+ service.boostEnergy(amount);
145
+ }, []),
146
+ playAnimation: useCallback(
147
+ (animationId: string, opts?: AnimationOptions) => {
148
+ const service = serviceRef.current;
149
+ if (!service) throw new Error('Service not initialized');
150
+ return service.playAnimation(animationId, opts);
151
+ },
152
+ []
153
+ ),
154
+ stopAnimation: useCallback(() => {
155
+ const service = serviceRef.current;
156
+ if (!service) throw new Error('Service not initialized');
157
+ service.stopAnimation();
158
+ }, []),
159
+ pauseAnimation: useCallback(() => {
160
+ const service = serviceRef.current;
161
+ if (!service) throw new Error('Service not initialized');
162
+ service.pauseAnimation();
163
+ }, []),
164
+ resumeAnimation: useCallback(() => {
165
+ const service = serviceRef.current;
166
+ if (!service) throw new Error('Service not initialized');
167
+ service.resumeAnimation();
168
+ }, []),
169
+ updateAppearance: useCallback((appearance: Partial<MascotAppearance>) => {
170
+ const service = serviceRef.current;
171
+ if (!service) throw new Error('Service not initialized');
172
+ service.updateAppearance(appearance);
173
+ }, []),
174
+ setBaseColor: useCallback((color: string) => {
175
+ const service = serviceRef.current;
176
+ if (!service) throw new Error('Service not initialized');
177
+ service.setBaseColor(color);
178
+ }, []),
179
+ setAccentColor: useCallback((color: string) => {
180
+ const service = serviceRef.current;
181
+ if (!service) throw new Error('Service not initialized');
182
+ service.setAccentColor(color);
183
+ }, []),
184
+ addAccessory: useCallback(
185
+ (accessory: { id: string; type: string; color?: string; position?: { x: number; y: number } }) => {
186
+ const service = serviceRef.current;
187
+ if (!service) throw new Error('Service not initialized');
188
+ service.addAccessory(accessory);
189
+ },
190
+ []
191
+ ),
192
+ removeAccessory: useCallback((accessoryId: string) => {
193
+ const service = serviceRef.current;
194
+ if (!service) throw new Error('Service not initialized');
195
+ service.removeAccessory(accessoryId);
196
+ }, []),
197
+ setVisible: useCallback((visible: boolean) => {
198
+ const service = serviceRef.current;
199
+ if (!service) throw new Error('Service not initialized');
200
+ service.setVisible(visible);
201
+ }, []),
202
+ setPosition: useCallback((position: { x: number; y: number }) => {
203
+ const service = serviceRef.current;
204
+ if (!service) throw new Error('Service not initialized');
205
+ service.setPosition(position);
206
+ }, []),
223
207
  };
224
208
  }
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * useMascotAnimation Hook
3
- * Advanced animation control with queue and sequencing
3
+ * Simplified - delegates to MascotService
4
4
  */
5
5
 
6
- import { useCallback, useRef, useState } from 'react';
7
- import type { Mascot } from '../../domain/entities/Mascot';
8
- import type { AnimationSpeed } from '../../domain/types/MascotTypes';
9
- import { AnimationController } from '../../infrastructure/controllers/AnimationController';
10
- import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
6
+ import { useCallback, useState } from 'react';
7
+ import type { AnimationSpeed, AnimationOptions } from '../../domain/types/MascotTypes';
8
+ import { DIContainer } from '../../infrastructure/di/Container';
11
9
 
12
10
  export interface UseMascotAnimationOptions {
13
- mascot: Mascot | null;
14
- autoplay?: boolean;
15
- queue?: boolean;
16
11
  speed?: AnimationSpeed;
17
12
  }
18
13
 
@@ -42,124 +37,83 @@ const SPEED_MULTIPLIERS: Record<AnimationSpeed, number> = {
42
37
  };
43
38
 
44
39
  export function useMascotAnimation(
45
- options: UseMascotAnimationOptions
40
+ options: UseMascotAnimationOptions = {}
46
41
  ): UseMascotAnimationReturn {
47
- const { mascot, speed = 'normal' } = options;
48
-
49
- const [isPlaying, setIsPlaying] = useState(false);
50
- const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
51
- const [progress, setProgress] = useState(0);
42
+ const { speed = 'normal' } = options;
52
43
  const [queue, setQueue] = useState<string[]>([]);
53
44
 
54
- const animationControllerRef = useRef<AnimationController | null>(null);
55
- const isProcessingQueueRef = useRef(false);
56
-
57
- // Initialize animation controller
58
- if (!animationControllerRef.current) {
59
- animationControllerRef.current = new AnimationController();
60
- }
61
-
62
- // Setup progress tracking
63
- const animationController = animationControllerRef.current;
64
- animationController.on('progress', (data: unknown) => {
65
- const { progress: newProgress } = data as { progress: number };
66
- setProgress(newProgress);
67
- });
68
-
69
- const play = useCallback(async (animationId: string, options?: AnimationOptions) => {
70
- if (!mascot) {
71
- console.warn('Mascot not initialized');
72
- return;
73
- }
74
-
75
- const animation = mascot.getAnimation(animationId);
76
- if (!animation) {
77
- console.warn(`Animation ${animationId} not found`);
78
- return;
79
- }
80
-
81
- setIsPlaying(true);
82
- setCurrentAnimation(animationId);
83
-
84
- const speedMultiplier = SPEED_MULTIPLIERS[speed];
85
- const finalOptions: AnimationOptions = {
86
- ...options,
87
- speed: (options?.speed || 1) * speedMultiplier,
88
- };
89
-
90
- await animationController.play(animation, finalOptions);
91
-
92
- setIsPlaying(false);
93
- setCurrentAnimation(null);
94
- setProgress(0);
95
- }, [mascot, speed, animationController]);
45
+ const container = DIContainer.getInstance();
46
+ const service = container.getMascotService();
47
+
48
+ const play = useCallback(
49
+ async (animationId: string, options?: AnimationOptions) => {
50
+ const speedMultiplier = SPEED_MULTIPLIERS[speed];
51
+ const finalOptions: AnimationOptions = {
52
+ ...options,
53
+ speed: (options?.speed || 1) * speedMultiplier,
54
+ };
55
+ await service.playAnimation(animationId, finalOptions);
56
+ },
57
+ [service, speed]
58
+ );
96
59
 
97
60
  const pause = useCallback(() => {
98
- animationController.pause();
99
- }, [animationController]);
61
+ service.pauseAnimation();
62
+ }, [service]);
100
63
 
101
64
  const resume = useCallback(() => {
102
- animationController.resume();
103
- }, [animationController]);
65
+ service.resumeAnimation();
66
+ }, [service]);
104
67
 
105
68
  const stop = useCallback(() => {
106
- animationController.stop();
107
- setIsPlaying(false);
108
- setCurrentAnimation(null);
109
- setProgress(0);
110
- }, [animationController]);
69
+ service.stopAnimation();
70
+ }, [service]);
111
71
 
112
- const setSpeed = useCallback((newSpeed: number) => {
113
- animationController.setSpeed(newSpeed);
114
- }, [animationController]);
72
+ const setSpeed = useCallback((_speed: number) => {
73
+ // Speed is handled via options in play() - this method is kept for API compatibility
74
+ // but does nothing as speed should be passed to play() directly
75
+ }, []);
115
76
 
116
- const setProgressValue = useCallback((newProgress: number) => {
117
- animationController.setProgress(newProgress);
118
- setProgress(newProgress);
119
- }, [animationController]);
77
+ const setProgress = useCallback((_progress: number) => {
78
+ // Progress tracking not yet implemented - would need AnimationController reference
79
+ }, []);
120
80
 
121
81
  const queueAnimation = useCallback((animationId: string) => {
122
- setQueue((prev) => [...prev, animationId]);
82
+ setQueue((prev: string[]) => [...prev, animationId]);
123
83
  }, []);
124
84
 
125
85
  const clearQueue = useCallback(() => {
126
86
  setQueue([]);
127
87
  }, []);
128
88
 
129
- const playSequence = useCallback(async (animationIds: string[]) => {
130
- for (const animationId of animationIds) {
131
- await play(animationId);
132
- }
133
- }, [play]);
89
+ const playSequence = useCallback(
90
+ async (animationIds: string[]) => {
91
+ for (const animationId of animationIds) {
92
+ await play(animationId);
93
+ }
94
+ },
95
+ [play]
96
+ );
134
97
 
135
- // Process queue automatically
136
98
  const processQueue = useCallback(async () => {
137
- if (isProcessingQueueRef.current || queue.length === 0 || !mascot) {
138
- return;
139
- }
140
-
141
- isProcessingQueueRef.current = true;
142
-
143
99
  while (queue.length > 0) {
144
100
  const nextAnimation = queue[0];
145
- setQueue((prev) => prev.slice(1));
101
+ setQueue((prev: string[]) => prev.slice(1));
146
102
  await play(nextAnimation);
147
103
  }
148
-
149
- isProcessingQueueRef.current = false;
150
- }, [queue, mascot, play]);
104
+ }, [play, queue]);
151
105
 
152
106
  return {
153
- isPlaying,
154
- currentAnimation,
155
- progress,
107
+ isPlaying: service.isPlaying,
108
+ currentAnimation: service.currentAnimation,
109
+ progress: 0, // Would need AnimationController reference
156
110
  queue,
157
111
  play,
158
112
  pause,
159
113
  resume,
160
114
  stop,
161
115
  setSpeed,
162
- setProgress: setProgressValue,
116
+ setProgress,
163
117
  queueAnimation,
164
118
  clearQueue,
165
119
  playSequence,