@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.
- package/README.md +60 -0
- package/package.json +2 -1
- package/src/application/dto/MascotDTO.ts +64 -0
- package/src/application/errors/MascotErrors.ts +76 -0
- package/src/application/services/AnimationStateManager.ts +69 -0
- package/src/application/services/AppearanceManagement.ts +40 -0
- package/src/application/services/MascotService.ts +203 -0
- package/src/application/services/PersonalityManagement.ts +39 -0
- package/src/application/services/StateHistory.ts +55 -0
- package/src/application/services/StateMachine.ts +154 -0
- package/src/application/services/StateTransitions.ts +73 -0
- package/src/application.ts +40 -0
- package/src/assets/index.ts +14 -19
- package/src/core.ts +62 -0
- package/src/domain/entities/Mascot.ts +197 -99
- package/src/domain/types/AnimationStateTypes.ts +148 -0
- package/src/domain/types/MascotTypes.ts +9 -0
- package/src/domain/value-objects/AnimationState.ts +126 -0
- package/src/domain/value-objects/EnergyLevel.ts +80 -0
- package/src/domain/value-objects/FriendlinessLevel.ts +66 -0
- package/src/domain/value-objects/Mood.ts +59 -0
- package/src/domain/value-objects/PlayfulnessLevel.ts +66 -0
- package/src/index.ts +16 -68
- package/src/infrastructure/controllers/AnimationController.ts +26 -122
- package/src/infrastructure/controllers/AnimationPlayer.ts +104 -0
- package/src/infrastructure/controllers/AnimationTimer.ts +62 -0
- package/src/infrastructure/controllers/EventManager.ts +108 -0
- package/src/infrastructure/di/Container.ts +153 -0
- package/src/infrastructure/managers/AssetManager.ts +134 -63
- package/src/infrastructure/managers/MascotBuilder.ts +89 -0
- package/src/infrastructure/managers/MascotFactory.ts +24 -176
- package/src/infrastructure/managers/MascotTemplates.ts +151 -0
- package/src/infrastructure/utils/LRUCache.ts +218 -0
- package/src/infrastructure.ts +24 -0
- package/src/presentation/components/LottieMascot.tsx +85 -0
- package/src/presentation/components/MascotView.tsx +42 -233
- package/src/presentation/components/SVGMascot.tsx +61 -0
- package/src/presentation/contexts/MascotContext.tsx +28 -111
- package/src/presentation/hooks/useMascot.ts +153 -169
- package/src/presentation/hooks/useMascotAnimation.ts +48 -94
- package/src/presentation/hooks/useMascotState.ts +213 -0
- package/src/presentation.ts +37 -0
- package/src/types.d.ts +4 -0
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useMascot Hook
|
|
3
|
-
*
|
|
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?:
|
|
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
|
-
|
|
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 [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
65
|
+
const serviceRef = useRef<MascotService | null>(null);
|
|
61
66
|
|
|
62
|
-
// Initialize
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
service.fromTemplate(initialTemplate);
|
|
202
97
|
}
|
|
203
|
-
}, [autoInitialize, initialConfig, initialTemplate
|
|
98
|
+
}, [autoInitialize, initialConfig, initialTemplate]);
|
|
204
99
|
|
|
100
|
+
// ✅ All methods delegate to service - NO business logic!
|
|
205
101
|
return {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
*
|
|
3
|
+
* Simplified - delegates to MascotService
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useCallback,
|
|
7
|
-
import type {
|
|
8
|
-
import
|
|
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 {
|
|
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
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
99
|
-
}, [
|
|
61
|
+
service.pauseAnimation();
|
|
62
|
+
}, [service]);
|
|
100
63
|
|
|
101
64
|
const resume = useCallback(() => {
|
|
102
|
-
|
|
103
|
-
}, [
|
|
65
|
+
service.resumeAnimation();
|
|
66
|
+
}, [service]);
|
|
104
67
|
|
|
105
68
|
const stop = useCallback(() => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
setCurrentAnimation(null);
|
|
109
|
-
setProgress(0);
|
|
110
|
-
}, [animationController]);
|
|
69
|
+
service.stopAnimation();
|
|
70
|
+
}, [service]);
|
|
111
71
|
|
|
112
|
-
const setSpeed = useCallback((
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
116
|
+
setProgress,
|
|
163
117
|
queueAnimation,
|
|
164
118
|
clearQueue,
|
|
165
119
|
playSequence,
|