@umituz/react-native-mascot 1.0.2 → 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
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayfulnessLevel Value Object
|
|
3
|
+
* Encapsulates playfulness validation and business rules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class PlayfulnessLevel {
|
|
7
|
+
private readonly MIN = 0;
|
|
8
|
+
private readonly MAX = 1;
|
|
9
|
+
private readonly PLAYFUL_THRESHOLD = 0.5;
|
|
10
|
+
private readonly VERY_PLAYFUL_THRESHOLD = 0.8;
|
|
11
|
+
|
|
12
|
+
private constructor(public readonly value: number) {
|
|
13
|
+
if (value < this.MIN || value > this.MAX) {
|
|
14
|
+
throw new Error(`Playfulness level must be between ${this.MIN} and ${this.MAX}, got: ${value}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static create(value: number): PlayfulnessLevel {
|
|
19
|
+
return new PlayfulnessLevel(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if mascot is playful
|
|
24
|
+
*/
|
|
25
|
+
isPlayful(): boolean {
|
|
26
|
+
return this.value >= this.PLAYFUL_THRESHOLD;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if mascot is very playful
|
|
31
|
+
*/
|
|
32
|
+
isVeryPlayful(): boolean {
|
|
33
|
+
return this.value >= this.VERY_PLAYFUL_THRESHOLD;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if mascot is serious
|
|
38
|
+
*/
|
|
39
|
+
isSerious(): boolean {
|
|
40
|
+
return this.value < 0.3;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Increase playfulness
|
|
45
|
+
*/
|
|
46
|
+
increase(amount: number): PlayfulnessLevel {
|
|
47
|
+
const newValue = Math.min(this.MAX, this.value + amount);
|
|
48
|
+
return PlayfulnessLevel.create(newValue);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Decrease playfulness
|
|
53
|
+
*/
|
|
54
|
+
decrease(amount: number): PlayfulnessLevel {
|
|
55
|
+
const newValue = Math.max(this.MIN, this.value - amount);
|
|
56
|
+
return PlayfulnessLevel.create(newValue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
equals(other: PlayfulnessLevel): boolean {
|
|
60
|
+
return this.value === other.value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
toJSON(): number {
|
|
64
|
+
return this.value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value Objects
|
|
3
|
+
* Encapsulate domain logic and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { Mood } from './Mood';
|
|
7
|
+
export { EnergyLevel } from './EnergyLevel';
|
|
8
|
+
export { FriendlinessLevel } from './FriendlinessLevel';
|
|
9
|
+
export { PlayfulnessLevel } from './PlayfulnessLevel';
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @umituz/react-native-mascot
|
|
3
3
|
*
|
|
4
|
-
* Interactive mascot system for React Native apps
|
|
4
|
+
* Interactive mascot system for React Native apps with DDD architecture
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { MascotMood, AnimationSpeed } from './domain/types/MascotTypes';
|
|
@@ -9,6 +9,9 @@ import type { MascotMood, AnimationSpeed } from './domain/types/MascotTypes';
|
|
|
9
9
|
// Domain - Entities
|
|
10
10
|
export { Mascot } from './domain/entities/Mascot';
|
|
11
11
|
|
|
12
|
+
// Domain - Value Objects
|
|
13
|
+
export { Mood, EnergyLevel, FriendlinessLevel, PlayfulnessLevel } from './domain/value-objects';
|
|
14
|
+
|
|
12
15
|
// Domain - Types
|
|
13
16
|
export type {
|
|
14
17
|
MascotType,
|
|
@@ -41,6 +44,35 @@ export type {
|
|
|
41
44
|
IMascotRepository,
|
|
42
45
|
} from './domain/interfaces/IMascotRepository';
|
|
43
46
|
|
|
47
|
+
// Application - Services
|
|
48
|
+
export { MascotService } from './application/services/MascotService';
|
|
49
|
+
export type { MascotTemplate } from './application/services/MascotService';
|
|
50
|
+
|
|
51
|
+
// Application - Errors
|
|
52
|
+
export {
|
|
53
|
+
MascotError,
|
|
54
|
+
MascotNotInitializedError,
|
|
55
|
+
AnimationNotFoundError,
|
|
56
|
+
InvalidEnergyLevelError,
|
|
57
|
+
InvalidFriendlinessLevelError,
|
|
58
|
+
InvalidPlayfulnessLevelError,
|
|
59
|
+
InvalidMoodTransitionError,
|
|
60
|
+
MascotNotFoundError,
|
|
61
|
+
TemplateNotFoundError,
|
|
62
|
+
} from './application/errors/MascotErrors';
|
|
63
|
+
|
|
64
|
+
// Application - DTOs
|
|
65
|
+
export type {
|
|
66
|
+
MascotDTO,
|
|
67
|
+
AnimationStateDTO,
|
|
68
|
+
MascotInitOptionsDTO,
|
|
69
|
+
AnimationPlaybackOptionsDTO,
|
|
70
|
+
MascotUpdateOptionsDTO,
|
|
71
|
+
} from './application/dto/MascotDTO';
|
|
72
|
+
|
|
73
|
+
// Infrastructure - DI
|
|
74
|
+
export { DIContainer } from './infrastructure/di/Container';
|
|
75
|
+
|
|
44
76
|
// Infrastructure - Repositories
|
|
45
77
|
export { MascotRepository } from './infrastructure/repositories/MascotRepository';
|
|
46
78
|
|
|
@@ -49,7 +81,7 @@ export { AnimationController } from './infrastructure/controllers/AnimationContr
|
|
|
49
81
|
|
|
50
82
|
// Infrastructure - Managers
|
|
51
83
|
export { AssetManager } from './infrastructure/managers/AssetManager';
|
|
52
|
-
export { MascotFactory, type MascotTemplate } from './infrastructure/managers/MascotFactory';
|
|
84
|
+
export { MascotFactory, type MascotTemplate as FactoryMascotTemplate } from './infrastructure/managers/MascotFactory';
|
|
53
85
|
|
|
54
86
|
// Presentation - Components
|
|
55
87
|
export { MascotView } from './presentation/components/MascotView';
|
|
@@ -97,3 +129,9 @@ export const DEFAULT_ANIMATION_SPEEDS: AnimationSpeed[] = [
|
|
|
97
129
|
'fast',
|
|
98
130
|
'very-fast',
|
|
99
131
|
];
|
|
132
|
+
|
|
133
|
+
// Convenience export - get service instance
|
|
134
|
+
export const getMascotService = () => {
|
|
135
|
+
const { DIContainer } = require('./infrastructure/di/Container');
|
|
136
|
+
return DIContainer.getInstance().getMascotService();
|
|
137
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Injection Container
|
|
3
|
+
* Manages singleton instances and dependencies
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IMascotRepository } from '../../domain/interfaces/IMascotRepository';
|
|
7
|
+
import type { MascotService } from '../../application/services/MascotService';
|
|
8
|
+
import { AnimationController } from '../controllers/AnimationController';
|
|
9
|
+
import { AssetManager } from '../managers/AssetManager';
|
|
10
|
+
import { MascotRepository } from '../repositories/MascotRepository';
|
|
11
|
+
|
|
12
|
+
export class DIContainer {
|
|
13
|
+
private static _instance: DIContainer;
|
|
14
|
+
private _animationController: AnimationController | null = null;
|
|
15
|
+
private _assetManager: AssetManager | null = null;
|
|
16
|
+
private _repository: IMascotRepository | null = null;
|
|
17
|
+
private _mascotService: MascotService | null = null;
|
|
18
|
+
|
|
19
|
+
private constructor() {}
|
|
20
|
+
|
|
21
|
+
static getInstance(): DIContainer {
|
|
22
|
+
if (!this._instance) {
|
|
23
|
+
this._instance = new DIContainer();
|
|
24
|
+
}
|
|
25
|
+
return this._instance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get or create AnimationController singleton
|
|
30
|
+
*/
|
|
31
|
+
getAnimationController(): AnimationController {
|
|
32
|
+
if (!this._animationController) {
|
|
33
|
+
this._animationController = new AnimationController();
|
|
34
|
+
}
|
|
35
|
+
return this._animationController;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get or create AssetManager singleton
|
|
40
|
+
*/
|
|
41
|
+
getAssetManager(): AssetManager {
|
|
42
|
+
if (!this._assetManager) {
|
|
43
|
+
this._assetManager = new AssetManager();
|
|
44
|
+
}
|
|
45
|
+
return this._assetManager;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get or create MascotRepository singleton
|
|
50
|
+
*/
|
|
51
|
+
getRepository(): IMascotRepository {
|
|
52
|
+
if (!this._repository) {
|
|
53
|
+
this._repository = new MascotRepository();
|
|
54
|
+
}
|
|
55
|
+
return this._repository;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get or create MascotService singleton
|
|
60
|
+
*/
|
|
61
|
+
getMascotService(): MascotService {
|
|
62
|
+
if (!this._mascotService) {
|
|
63
|
+
// Lazy import to avoid circular dependencies
|
|
64
|
+
const { MascotService } = require('../../application/services/MascotService');
|
|
65
|
+
this._mascotService = new MascotService(
|
|
66
|
+
this.getRepository(),
|
|
67
|
+
this.getAnimationController(),
|
|
68
|
+
this.getAssetManager()
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return this._mascotService;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Reset all instances (useful for testing)
|
|
76
|
+
*/
|
|
77
|
+
reset(): void {
|
|
78
|
+
this._animationController = null;
|
|
79
|
+
this._assetManager = null;
|
|
80
|
+
this._repository = null;
|
|
81
|
+
this._mascotService = null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if container has been initialized
|
|
86
|
+
*/
|
|
87
|
+
isInitialized(): boolean {
|
|
88
|
+
return this._mascotService !== null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -1,135 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* MascotContext
|
|
3
|
+
* Thin wrapper that provides MascotService to components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { createContext, useContext,
|
|
7
|
-
import { Mascot } from '../../domain/entities/Mascot';
|
|
6
|
+
import React, { createContext, useContext, ReactNode } from 'react';
|
|
7
|
+
import type { Mascot } from '../../domain/entities/Mascot';
|
|
8
8
|
import type { MascotConfig, MascotMood } from '../../domain/types/MascotTypes';
|
|
9
|
-
import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
|
|
10
|
-
import { AnimationController } from '../../infrastructure/controllers/AnimationController';
|
|
11
9
|
import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
|
|
10
|
+
import type { MascotService, MascotTemplate } from '../../application/services/MascotService';
|
|
11
|
+
import { DIContainer } from '../../infrastructure/di/Container';
|
|
12
12
|
|
|
13
13
|
export interface MascotContextValue {
|
|
14
14
|
mascot: Mascot | null;
|
|
15
15
|
isPlaying: boolean;
|
|
16
16
|
currentAnimation: string | null;
|
|
17
|
-
|
|
18
|
-
initializeFromTemplate: (template: string, customizations?: Partial<MascotConfig>) => void;
|
|
19
|
-
setMood: (mood: MascotMood) => void;
|
|
20
|
-
playAnimation: (animationId: string, options?: AnimationOptions) => Promise<void>;
|
|
21
|
-
stopAnimation: () => void;
|
|
22
|
-
updateAppearance: (appearance: Partial<MascotConfig['appearance']>) => void;
|
|
23
|
-
setVisible: (visible: boolean) => void;
|
|
17
|
+
service: MascotService;
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
const MascotContext = createContext<MascotContextValue | undefined>(undefined);
|
|
27
21
|
|
|
28
|
-
export interface MascotProviderProps
|
|
22
|
+
export interface MascotProviderProps {
|
|
23
|
+
children: ReactNode;
|
|
29
24
|
initialConfig?: MascotConfig;
|
|
30
|
-
template?:
|
|
25
|
+
template?: MascotTemplate;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
export const MascotProvider: React.FC<MascotProviderProps> = ({
|
|
34
29
|
children,
|
|
35
|
-
initialConfig
|
|
36
|
-
template
|
|
30
|
+
initialConfig,
|
|
31
|
+
template,
|
|
37
32
|
}) => {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
animationControllerRef.current = new AnimationController();
|
|
48
|
-
}
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
const initializeFromTemplate = useCallback((
|
|
52
|
-
templateName: string,
|
|
53
|
-
customizations?: Partial<MascotConfig>
|
|
54
|
-
) => {
|
|
55
|
-
const template = templateName as 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero';
|
|
56
|
-
const newMascot = MascotFactory.createFromTemplate(template, customizations);
|
|
57
|
-
setMascot(newMascot);
|
|
58
|
-
if (!animationControllerRef.current) {
|
|
59
|
-
animationControllerRef.current = new AnimationController();
|
|
60
|
-
}
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
|
-
const setMood = useCallback((mood: MascotMood) => {
|
|
64
|
-
setMascot((prev) => {
|
|
65
|
-
if (!prev) return null;
|
|
66
|
-
prev.setMood(mood);
|
|
67
|
-
return prev.clone();
|
|
68
|
-
});
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
const playAnimation = useCallback(async (animationId: string, options?: AnimationOptions) => {
|
|
72
|
-
if (!mascot || !animationControllerRef.current) return;
|
|
73
|
-
|
|
74
|
-
const animation = mascot.getAnimation(animationId);
|
|
75
|
-
if (!animation) {
|
|
76
|
-
console.warn(`Animation ${animationId} not found`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
setIsPlaying(true);
|
|
81
|
-
setCurrentAnimation(animationId);
|
|
82
|
-
|
|
83
|
-
if (animationControllerRef.current) {
|
|
84
|
-
await animationControllerRef.current.play(animation, options);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setIsPlaying(false);
|
|
88
|
-
setCurrentAnimation(null);
|
|
89
|
-
}, [mascot]);
|
|
90
|
-
|
|
91
|
-
const stopAnimation = useCallback(() => {
|
|
92
|
-
if (animationControllerRef.current) {
|
|
93
|
-
animationControllerRef.current.stop();
|
|
94
|
-
}
|
|
95
|
-
setIsPlaying(false);
|
|
96
|
-
setCurrentAnimation(null);
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
const updateAppearance = useCallback((appearance: Partial<MascotConfig['appearance']>) => {
|
|
100
|
-
setMascot((prev) => {
|
|
101
|
-
if (!prev) return null;
|
|
102
|
-
prev.updateAppearance(appearance);
|
|
103
|
-
return prev.clone();
|
|
104
|
-
});
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
const setVisible = useCallback((visible: boolean) => {
|
|
108
|
-
setMascot((prev) => {
|
|
109
|
-
if (!prev) return null;
|
|
110
|
-
prev.setVisible(visible);
|
|
111
|
-
return prev.clone();
|
|
112
|
-
});
|
|
113
|
-
}, []);
|
|
33
|
+
const container = DIContainer.getInstance();
|
|
34
|
+
const service = container.getMascotService();
|
|
35
|
+
|
|
36
|
+
// Auto-initialize if config or template provided
|
|
37
|
+
if (initialConfig) {
|
|
38
|
+
service.initialize(initialConfig);
|
|
39
|
+
} else if (template) {
|
|
40
|
+
service.fromTemplate(template);
|
|
41
|
+
}
|
|
114
42
|
|
|
115
43
|
const value: MascotContextValue = {
|
|
116
|
-
mascot,
|
|
117
|
-
isPlaying,
|
|
118
|
-
currentAnimation,
|
|
119
|
-
|
|
120
|
-
initializeFromTemplate,
|
|
121
|
-
setMood,
|
|
122
|
-
playAnimation,
|
|
123
|
-
stopAnimation,
|
|
124
|
-
updateAppearance,
|
|
125
|
-
setVisible,
|
|
44
|
+
mascot: service.mascot,
|
|
45
|
+
isPlaying: service.isPlaying,
|
|
46
|
+
currentAnimation: service.currentAnimation,
|
|
47
|
+
service,
|
|
126
48
|
};
|
|
127
49
|
|
|
128
|
-
return
|
|
129
|
-
<MascotContext.Provider value={value}>
|
|
130
|
-
{children}
|
|
131
|
-
</MascotContext.Provider>
|
|
132
|
-
);
|
|
50
|
+
return <MascotContext.Provider value={value}>{children}</MascotContext.Provider>;
|
|
133
51
|
};
|
|
134
52
|
|
|
135
53
|
export const useMascotContext = (): MascotContextValue => {
|