@umituz/react-native-mascot 1.0.3 → 1.0.4
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 +1 -1
- package/src/application/dto/MascotDTO.ts +64 -0
- package/src/application/errors/MascotErrors.ts +76 -0
- package/src/application/index.ts +8 -0
- package/src/application/services/MascotService.ts +194 -0
- package/src/domain/entities/Mascot.ts +59 -20
- 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/domain/value-objects/index.ts +9 -0
- package/src/index.ts +40 -2
- package/src/infrastructure/di/Container.ts +90 -0
- package/src/presentation/contexts/MascotContext.tsx +26 -108
- package/src/presentation/hooks/useMascot.ts +77 -172
- package/src/presentation/hooks/useMascotAnimation.ts +48 -88
|
@@ -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,77 @@ export interface UseMascotReturn {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
export function useMascot(options: UseMascotOptions = {}): UseMascotReturn {
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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();
|
|
56
|
+
const { config: initialConfig, template: initialTemplate, autoInitialize = true } = options;
|
|
57
|
+
|
|
58
|
+
const [state, setState] = useState(() => ({
|
|
59
|
+
mascot: null as Mascot | null,
|
|
60
|
+
isReady: false,
|
|
61
|
+
isPlaying: false,
|
|
62
|
+
currentAnimation: null as string | null,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
const serviceRef = useRef<MascotService | null>(null);
|
|
66
|
+
|
|
67
|
+
// ✅ Initialize service once
|
|
68
|
+
if (!serviceRef.current) {
|
|
69
|
+
const container = DIContainer.getInstance();
|
|
70
|
+
serviceRef.current = container.getMascotService();
|
|
71
|
+
|
|
72
|
+
// Subscribe to service changes
|
|
73
|
+
serviceRef.current.subscribe(() => {
|
|
74
|
+
const service = serviceRef.current!;
|
|
75
|
+
setState({
|
|
76
|
+
mascot: service.mascot,
|
|
77
|
+
isReady: service.isReady,
|
|
78
|
+
isPlaying: service.isPlaying,
|
|
79
|
+
currentAnimation: service.currentAnimation,
|
|
80
|
+
});
|
|
101
81
|
});
|
|
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();
|
|
128
|
-
}
|
|
129
|
-
setIsPlaying(false);
|
|
130
|
-
setCurrentAnimation(null);
|
|
131
|
-
}, []);
|
|
82
|
+
}
|
|
132
83
|
|
|
133
|
-
|
|
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
|
-
});
|
|
194
|
-
}, []);
|
|
84
|
+
const service = serviceRef.current;
|
|
195
85
|
|
|
196
|
-
// Auto-initialize
|
|
86
|
+
// ✅ Auto-initialize
|
|
197
87
|
useEffect(() => {
|
|
198
88
|
if (autoInitialize && initialConfig) {
|
|
199
|
-
initialize(initialConfig);
|
|
89
|
+
service.initialize(initialConfig);
|
|
200
90
|
} else if (autoInitialize && initialTemplate) {
|
|
201
|
-
|
|
91
|
+
service.fromTemplate(initialTemplate);
|
|
202
92
|
}
|
|
203
|
-
}, [autoInitialize, initialConfig, initialTemplate
|
|
93
|
+
}, [autoInitialize, initialConfig, initialTemplate]);
|
|
204
94
|
|
|
95
|
+
// ✅ All methods delegate to service - NO business logic!
|
|
205
96
|
return {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
97
|
+
...state,
|
|
98
|
+
initialize: useCallback((config: MascotConfig) => service.initialize(config), [service]),
|
|
99
|
+
fromTemplate: useCallback(
|
|
100
|
+
(template: MascotTemplate, customizations?: Partial<MascotConfig>) =>
|
|
101
|
+
service.fromTemplate(template, customizations),
|
|
102
|
+
[service]
|
|
103
|
+
),
|
|
104
|
+
setMood: useCallback((mood: MascotMood) => service.setMood(mood), [service]),
|
|
105
|
+
setEnergy: useCallback((energy: number) => service.setEnergy(energy), [service]),
|
|
106
|
+
setFriendliness: useCallback((friendliness: number) => service.setFriendliness(friendliness), [service]),
|
|
107
|
+
setPlayfulness: useCallback((playfulness: number) => service.setPlayfulness(playfulness), [service]),
|
|
108
|
+
cheerUp: useCallback(() => service.cheerUp(), [service]),
|
|
109
|
+
boostEnergy: useCallback((amount: number) => service.boostEnergy(amount), [service]),
|
|
110
|
+
playAnimation: useCallback(
|
|
111
|
+
(animationId: string, opts?: AnimationOptions) => service.playAnimation(animationId, opts),
|
|
112
|
+
[service]
|
|
113
|
+
),
|
|
114
|
+
stopAnimation: useCallback(() => service.stopAnimation(), [service]),
|
|
115
|
+
pauseAnimation: useCallback(() => service.pauseAnimation(), [service]),
|
|
116
|
+
resumeAnimation: useCallback(() => service.resumeAnimation(), [service]),
|
|
117
|
+
updateAppearance: useCallback((appearance: Partial<MascotAppearance>) => service.updateAppearance(appearance), [service]),
|
|
118
|
+
setBaseColor: useCallback((color: string) => service.setBaseColor(color), [service]),
|
|
119
|
+
setAccentColor: useCallback((color: string) => service.setAccentColor(color), [service]),
|
|
120
|
+
addAccessory: useCallback(
|
|
121
|
+
(accessory: { id: string; type: string; color?: string; position?: { x: number; y: number } }) =>
|
|
122
|
+
service.addAccessory(accessory),
|
|
123
|
+
[service]
|
|
124
|
+
),
|
|
125
|
+
removeAccessory: useCallback((accessoryId: string) => service.removeAccessory(accessoryId), [service]),
|
|
126
|
+
setVisible: useCallback((visible: boolean) => service.setVisible(visible), [service]),
|
|
127
|
+
setPosition: useCallback((position: { x: number; y: number }) => service.setPosition(position), [service]),
|
|
223
128
|
};
|
|
224
129
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useMascotAnimation Hook
|
|
3
|
-
*
|
|
3
|
+
* Simplified - delegates to MascotService
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useCallback,
|
|
6
|
+
import { useCallback, useState } from 'react';
|
|
7
7
|
import type { Mascot } from '../../domain/entities/Mascot';
|
|
8
|
-
import type { AnimationSpeed } from '../../domain/types/MascotTypes';
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
8
|
+
import type { AnimationSpeed, AnimationOptions } from '../../domain/types/MascotTypes';
|
|
9
|
+
import type { MascotService } from '../../application/services/MascotService';
|
|
10
|
+
import { DIContainer } from '../../infrastructure/di/Container';
|
|
11
11
|
|
|
12
12
|
export interface UseMascotAnimationOptions {
|
|
13
|
-
mascot
|
|
13
|
+
mascot?: Mascot | null;
|
|
14
14
|
autoplay?: boolean;
|
|
15
15
|
queue?: boolean;
|
|
16
16
|
speed?: AnimationSpeed;
|
|
@@ -42,81 +42,47 @@ const SPEED_MULTIPLIERS: Record<AnimationSpeed, number> = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export function useMascotAnimation(
|
|
45
|
-
options: UseMascotAnimationOptions
|
|
45
|
+
options: UseMascotAnimationOptions = {}
|
|
46
46
|
): UseMascotAnimationReturn {
|
|
47
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);
|
|
52
48
|
const [queue, setQueue] = useState<string[]>([]);
|
|
53
49
|
|
|
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]);
|
|
50
|
+
const container = DIContainer.getInstance();
|
|
51
|
+
const service = container.getMascotService();
|
|
52
|
+
|
|
53
|
+
const play = useCallback(
|
|
54
|
+
async (animationId: string, options?: AnimationOptions) => {
|
|
55
|
+
const speedMultiplier = SPEED_MULTIPLIERS[speed];
|
|
56
|
+
const finalOptions: AnimationOptions = {
|
|
57
|
+
...options,
|
|
58
|
+
speed: (options?.speed || 1) * speedMultiplier,
|
|
59
|
+
};
|
|
60
|
+
await service.playAnimation(animationId, finalOptions);
|
|
61
|
+
},
|
|
62
|
+
[service, speed]
|
|
63
|
+
);
|
|
96
64
|
|
|
97
65
|
const pause = useCallback(() => {
|
|
98
|
-
|
|
99
|
-
}, [
|
|
66
|
+
service.pauseAnimation();
|
|
67
|
+
}, [service]);
|
|
100
68
|
|
|
101
69
|
const resume = useCallback(() => {
|
|
102
|
-
|
|
103
|
-
}, [
|
|
70
|
+
service.resumeAnimation();
|
|
71
|
+
}, [service]);
|
|
104
72
|
|
|
105
73
|
const stop = useCallback(() => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
setCurrentAnimation(null);
|
|
109
|
-
setProgress(0);
|
|
110
|
-
}, [animationController]);
|
|
74
|
+
service.stopAnimation();
|
|
75
|
+
}, [service]);
|
|
111
76
|
|
|
112
|
-
const setSpeed = useCallback((
|
|
113
|
-
|
|
114
|
-
|
|
77
|
+
const setSpeed = useCallback(() => {
|
|
78
|
+
// Speed is handled via options in play()
|
|
79
|
+
console.warn('setSpeed: Use play() with speed option instead');
|
|
80
|
+
}, []);
|
|
115
81
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
setProgress
|
|
119
|
-
}, [
|
|
82
|
+
const setProgress = useCallback(() => {
|
|
83
|
+
// Progress tracking would need AnimationController reference
|
|
84
|
+
console.warn('setProgress: Not implemented in service layer yet');
|
|
85
|
+
}, []);
|
|
120
86
|
|
|
121
87
|
const queueAnimation = useCallback((animationId: string) => {
|
|
122
88
|
setQueue((prev) => [...prev, animationId]);
|
|
@@ -126,40 +92,34 @@ export function useMascotAnimation(
|
|
|
126
92
|
setQueue([]);
|
|
127
93
|
}, []);
|
|
128
94
|
|
|
129
|
-
const playSequence = useCallback(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
95
|
+
const playSequence = useCallback(
|
|
96
|
+
async (animationIds: string[]) => {
|
|
97
|
+
for (const animationId of animationIds) {
|
|
98
|
+
await play(animationId);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[play]
|
|
102
|
+
);
|
|
134
103
|
|
|
135
|
-
// Process queue automatically
|
|
136
104
|
const processQueue = useCallback(async () => {
|
|
137
|
-
if (isProcessingQueueRef.current || queue.length === 0 || !mascot) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
isProcessingQueueRef.current = true;
|
|
142
|
-
|
|
143
105
|
while (queue.length > 0) {
|
|
144
106
|
const nextAnimation = queue[0];
|
|
145
107
|
setQueue((prev) => prev.slice(1));
|
|
146
108
|
await play(nextAnimation);
|
|
147
109
|
}
|
|
148
|
-
|
|
149
|
-
isProcessingQueueRef.current = false;
|
|
150
|
-
}, [queue, mascot, play]);
|
|
110
|
+
}, [queue, play]);
|
|
151
111
|
|
|
152
112
|
return {
|
|
153
|
-
isPlaying,
|
|
154
|
-
currentAnimation,
|
|
155
|
-
progress,
|
|
113
|
+
isPlaying: service.isPlaying,
|
|
114
|
+
currentAnimation: service.currentAnimation,
|
|
115
|
+
progress: 0, // Would need AnimationController reference
|
|
156
116
|
queue,
|
|
157
117
|
play,
|
|
158
118
|
pause,
|
|
159
119
|
resume,
|
|
160
120
|
stop,
|
|
161
121
|
setSpeed,
|
|
162
|
-
setProgress
|
|
122
|
+
setProgress,
|
|
163
123
|
queueAnimation,
|
|
164
124
|
clearQueue,
|
|
165
125
|
playSequence,
|