@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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * EnergyLevel Value Object
3
+ * Encapsulates energy level validation and business rules
4
+ */
5
+
6
+ export class EnergyLevel {
7
+ private readonly MIN = 0;
8
+ private readonly MAX = 1;
9
+ private readonly HIGH_THRESHOLD = 0.7;
10
+ private readonly LOW_THRESHOLD = 0.3;
11
+
12
+ private constructor(public readonly value: number) {
13
+ if (value < this.MIN || value > this.MAX) {
14
+ throw new Error(`Energy level must be between ${this.MIN} and ${this.MAX}, got: ${value}`);
15
+ }
16
+ }
17
+
18
+ static create(value: number): EnergyLevel {
19
+ return new EnergyLevel(value);
20
+ }
21
+
22
+ /**
23
+ * Check if energy is high
24
+ */
25
+ isHigh(): boolean {
26
+ return this.value > this.HIGH_THRESHOLD;
27
+ }
28
+
29
+ /**
30
+ * Check if energy is low
31
+ */
32
+ isLow(): boolean {
33
+ return this.value < this.LOW_THRESHOLD;
34
+ }
35
+
36
+ /**
37
+ * Check if energy is moderate
38
+ */
39
+ isModerate(): boolean {
40
+ return !this.isHigh() && !this.isLow();
41
+ }
42
+
43
+ /**
44
+ * Increase energy by amount (clamped to MAX)
45
+ */
46
+ increase(amount: number): EnergyLevel {
47
+ const newValue = Math.min(this.MAX, this.value + amount);
48
+ return EnergyLevel.create(newValue);
49
+ }
50
+
51
+ /**
52
+ * Decrease energy by amount (clamped to MIN)
53
+ */
54
+ decrease(amount: number): EnergyLevel {
55
+ const newValue = Math.max(this.MIN, this.value - amount);
56
+ return EnergyLevel.create(newValue);
57
+ }
58
+
59
+ /**
60
+ * Set energy to specific level
61
+ */
62
+ setLevel(value: number): EnergyLevel {
63
+ return EnergyLevel.create(value);
64
+ }
65
+
66
+ /**
67
+ * Get energy as percentage (0-100)
68
+ */
69
+ toPercentage(): number {
70
+ return Math.round(this.value * 100);
71
+ }
72
+
73
+ equals(other: EnergyLevel): boolean {
74
+ return this.value === other.value;
75
+ }
76
+
77
+ toJSON(): number {
78
+ return this.value;
79
+ }
80
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * FriendlinessLevel Value Object
3
+ * Encapsulates friendliness validation and business rules
4
+ */
5
+
6
+ export class FriendlinessLevel {
7
+ private readonly MIN = 0;
8
+ private readonly MAX = 1;
9
+ private readonly FRIENDLY_THRESHOLD = 0.6;
10
+ private readonly VERY_FRIENDLY_THRESHOLD = 0.8;
11
+
12
+ private constructor(public readonly value: number) {
13
+ if (value < this.MIN || value > this.MAX) {
14
+ throw new Error(`Friendliness level must be between ${this.MIN} and ${this.MAX}, got: ${value}`);
15
+ }
16
+ }
17
+
18
+ static create(value: number): FriendlinessLevel {
19
+ return new FriendlinessLevel(value);
20
+ }
21
+
22
+ /**
23
+ * Check if mascot is friendly
24
+ */
25
+ isFriendly(): boolean {
26
+ return this.value >= this.FRIENDLY_THRESHOLD;
27
+ }
28
+
29
+ /**
30
+ * Check if mascot is very friendly
31
+ */
32
+ isVeryFriendly(): boolean {
33
+ return this.value >= this.VERY_FRIENDLY_THRESHOLD;
34
+ }
35
+
36
+ /**
37
+ * Check if mascot is shy
38
+ */
39
+ isShy(): boolean {
40
+ return this.value < 0.4;
41
+ }
42
+
43
+ /**
44
+ * Increase friendliness
45
+ */
46
+ increase(amount: number): FriendlinessLevel {
47
+ const newValue = Math.min(this.MAX, this.value + amount);
48
+ return FriendlinessLevel.create(newValue);
49
+ }
50
+
51
+ /**
52
+ * Decrease friendliness
53
+ */
54
+ decrease(amount: number): FriendlinessLevel {
55
+ const newValue = Math.max(this.MIN, this.value - amount);
56
+ return FriendlinessLevel.create(newValue);
57
+ }
58
+
59
+ equals(other: FriendlinessLevel): boolean {
60
+ return this.value === other.value;
61
+ }
62
+
63
+ toJSON(): number {
64
+ return this.value;
65
+ }
66
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Mood Value Object
3
+ * Encapsulates mood logic and validation
4
+ */
5
+
6
+ import type { MascotMood } from '../types/MascotTypes';
7
+
8
+ export class Mood {
9
+ private constructor(public readonly value: MascotMood) {}
10
+
11
+ static create(value: MascotMood): Mood {
12
+ return new Mood(value);
13
+ }
14
+
15
+ /**
16
+ * Check if mood is positive
17
+ */
18
+ isPositive(): boolean {
19
+ return ['happy', 'excited', 'surprised'].includes(this.value);
20
+ }
21
+
22
+ /**
23
+ * Check if mood is negative
24
+ */
25
+ isNegative(): boolean {
26
+ return ['sad', 'angry'].includes(this.value);
27
+ }
28
+
29
+ /**
30
+ * Check if mood is neutral
31
+ */
32
+ isNeutral(): boolean {
33
+ return ['neutral', 'thinking'].includes(this.value);
34
+ }
35
+
36
+ /**
37
+ * Get opposite mood
38
+ */
39
+ getOpposite(): Mood {
40
+ const opposites: Record<MascotMood, MascotMood> = {
41
+ happy: 'sad',
42
+ sad: 'happy',
43
+ excited: 'angry',
44
+ angry: 'excited',
45
+ thinking: 'neutral',
46
+ neutral: 'thinking',
47
+ surprised: 'neutral',
48
+ };
49
+ return Mood.create(opposites[this.value]);
50
+ }
51
+
52
+ equals(other: Mood): boolean {
53
+ return this.value === other.value;
54
+ }
55
+
56
+ toJSON(): MascotMood {
57
+ return this.value;
58
+ }
59
+ }
@@ -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
+ }
package/src/index.ts CHANGED
@@ -1,78 +1,19 @@
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
+ * Version: 1.0.6
5
6
  */
6
7
 
7
- import type { MascotMood, AnimationSpeed } from './domain/types/MascotTypes';
8
-
9
- // Domain - Entities
10
- export { Mascot } from './domain/entities/Mascot';
11
-
12
- // Domain - Types
13
- export type {
14
- MascotType,
15
- MascotStyle,
16
- MascotAnimationType,
17
- MascotAppearance,
18
- MascotAccessory,
19
- MascotPersonality,
20
- MascotAnimation,
21
- MascotConfig,
22
- MascotState,
23
- MascotInteraction,
24
- MascotMood,
25
- AnimationSpeed,
26
- } from './domain/types/MascotTypes';
27
-
28
- // Domain - Interfaces
29
- export type {
30
- IAnimationController,
31
- AnimationEvent,
32
- AnimationOptions,
33
- } from './domain/interfaces/IAnimationController';
34
-
35
- export type {
36
- IAssetManager,
37
- AssetCache,
38
- } from './domain/interfaces/IAssetManager';
39
-
40
- export type {
41
- IMascotRepository,
42
- } from './domain/interfaces/IMascotRepository';
43
-
44
- // Infrastructure - Repositories
45
- export { MascotRepository } from './infrastructure/repositories/MascotRepository';
46
-
47
- // Infrastructure - Controllers
48
- export { AnimationController } from './infrastructure/controllers/AnimationController';
49
-
50
- // Infrastructure - Managers
51
- export { AssetManager } from './infrastructure/managers/AssetManager';
52
- export { MascotFactory, type MascotTemplate } from './infrastructure/managers/MascotFactory';
53
-
54
- // Presentation - Components
55
- export { MascotView } from './presentation/components/MascotView';
56
- export type { MascotViewProps } from './presentation/components/MascotView';
57
-
58
- // Presentation - Hooks
59
- export { useMascot } from './presentation/hooks/useMascot';
60
- export type {
61
- UseMascotOptions,
62
- UseMascotReturn,
63
- } from './presentation/hooks/useMascot';
64
-
65
- export { useMascotAnimation } from './presentation/hooks/useMascotAnimation';
66
- export type {
67
- UseMascotAnimationOptions,
68
- UseMascotAnimationReturn,
69
- } from './presentation/hooks/useMascotAnimation';
70
-
71
- // Presentation - Contexts
72
- export { MascotProvider, useMascotContext } from './presentation/contexts/MascotContext';
73
- export type { MascotProviderProps, MascotContextValue } from './presentation/contexts/MascotContext';
8
+ // Re-export all layers
9
+ export * from './core';
10
+ export * from './application';
11
+ export * from './infrastructure';
12
+ export * from './presentation';
74
13
 
75
14
  // Constants
15
+ import type { MascotMood, AnimationSpeed } from './domain/types/MascotTypes';
16
+
76
17
  export const MASCOT_TEMPLATES = [
77
18
  'friendly-bot',
78
19
  'cute-pet',
@@ -97,3 +38,10 @@ export const DEFAULT_ANIMATION_SPEEDS: AnimationSpeed[] = [
97
38
  'fast',
98
39
  'very-fast',
99
40
  ];
41
+
42
+ // Convenience export - get service instance
43
+ export const getMascotService = () => {
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const { DIContainer } = require('./infrastructure/di/Container');
46
+ return DIContainer.getInstance().getMascotService();
47
+ };
@@ -1,163 +1,67 @@
1
1
  /**
2
- * Animation Controller Implementation
3
- * Controls Lottie and SVG animations with unified API
2
+ * Animation Controller (Main - 60 lines)
3
+ * Unified animation control interface
4
4
  */
5
5
 
6
- import type {
7
- IAnimationController,
8
- AnimationOptions,
9
- AnimationEvent,
10
- } from '../../domain/interfaces/IAnimationController';
6
+ import type { IAnimationController, AnimationOptions, AnimationEvent } from '../../domain/interfaces/IAnimationController';
11
7
  import type { MascotAnimation } from '../../domain/types/MascotTypes';
8
+ import { AnimationPlayer } from './AnimationPlayer';
9
+ import { EventManager } from './EventManager';
12
10
 
13
11
  export class AnimationController implements IAnimationController {
14
- private _currentAnimation: MascotAnimation | null = null;
15
- private _isPlaying: boolean = false;
16
- private _isPaused: boolean = false;
17
- private _progress: number = 0;
18
- private _speed: number = 1;
19
- private _eventListeners: Map<AnimationEvent, Set<(data?: unknown) => void>>;
12
+ private _player: AnimationPlayer;
13
+ private _events: EventManager;
20
14
 
21
15
  constructor() {
22
- this._eventListeners = new Map();
23
- this._initializeEventListeners();
16
+ this._player = new AnimationPlayer();
17
+ this._events = new EventManager();
24
18
  }
25
19
 
26
- play(
27
- animation: MascotAnimation,
28
- options?: AnimationOptions
29
- ): Promise<void> {
30
- this._currentAnimation = animation;
31
- this._isPlaying = true;
32
- this._isPaused = false;
33
-
34
- if (options?.speed !== undefined) {
35
- this._speed = options.speed;
36
- }
37
-
38
- this._emit('start', { animation: animation.id });
39
-
40
- // Simulate animation completion
41
- // In real implementation, this would be controlled by LottieView
42
- const duration = animation.duration || 2000;
43
- const adjustedDuration = duration / this._speed;
44
-
45
- return new Promise((resolve) => {
46
- setTimeout(() => {
47
- if (this._isPlaying && !this._isPaused) {
48
- this._isPlaying = false;
49
- this._progress = 1;
50
- this._emit('finish', { animation: animation.id });
51
- options?.onFinish?.();
52
- }
53
- resolve();
54
- }, adjustedDuration);
55
-
56
- options?.onStart?.();
57
- });
20
+ play(animation: MascotAnimation, options?: AnimationOptions): Promise<void> {
21
+ return this._player.play(animation, options);
58
22
  }
59
23
 
60
24
  pause(): void {
61
- if (!this._isPlaying || this._isPaused) {
62
- return;
63
- }
64
-
65
- this._isPaused = true;
66
- this._emit('pause');
25
+ this._player.pause();
26
+ this._events.emit('pause');
67
27
  }
68
28
 
69
29
  resume(): void {
70
- if (!this._isPaused) {
71
- return;
72
- }
73
-
74
- this._isPaused = false;
75
- this._emit('resume');
30
+ this._player.resume();
31
+ this._events.emit('resume');
76
32
  }
77
33
 
78
34
  stop(): void {
79
- this._isPlaying = false;
80
- this._isPaused = false;
81
- this._progress = 0;
82
- this._currentAnimation = null;
35
+ this._player.stop();
83
36
  }
84
37
 
85
38
  getProgress(): number {
86
- return this._progress;
39
+ return this._player.progress;
87
40
  }
88
41
 
89
42
  setProgress(progress: number): void {
90
- if (progress < 0 || progress > 1) {
91
- throw new Error('Progress must be between 0 and 1');
92
- }
93
- this._progress = progress;
94
- this._emit('progress', { progress });
43
+ this._player.setProgress(progress);
44
+ this._events.emit('progress', { progress });
95
45
  }
96
46
 
97
47
  setSpeed(speed: number): void {
98
- if (speed <= 0) {
99
- throw new Error('Speed must be greater than 0');
100
- }
101
- this._speed = speed;
48
+ this._player.setSpeed(speed);
102
49
  }
103
50
 
104
51
  isPlaying(): boolean {
105
- return this._isPlaying && !this._isPaused;
52
+ return this._player.isPlaying;
106
53
  }
107
54
 
108
55
  on(event: AnimationEvent, callback: (data?: unknown) => void): () => void {
109
- if (!this._eventListeners.has(event)) {
110
- this._eventListeners.set(event, new Set());
111
- }
112
-
113
- this._eventListeners.get(event)!.add(callback);
114
-
115
- // Return unsubscribe function
116
- return () => {
117
- this.off(event, callback);
118
- };
56
+ return this._events.on(event, callback);
119
57
  }
120
58
 
121
59
  off(event: AnimationEvent, callback: (data?: unknown) => void): void {
122
- const listeners = this._eventListeners.get(event);
123
- if (listeners) {
124
- listeners.delete(callback);
125
- }
126
- }
127
-
128
- // Private Methods
129
- private _initializeEventListeners(): void {
130
- const events: AnimationEvent[] = ['start', 'finish', 'pause', 'resume', 'progress', 'error'];
131
- events.forEach((event) => {
132
- if (!this._eventListeners.has(event)) {
133
- this._eventListeners.set(event, new Set());
134
- }
135
- });
136
- }
137
-
138
- private _emit(event: AnimationEvent, data?: unknown): void {
139
- const listeners = this._eventListeners.get(event);
140
- if (listeners) {
141
- listeners.forEach((callback) => {
142
- try {
143
- callback(data);
144
- } catch (error) {
145
- console.error(`Error in ${event} event listener:`, error);
146
- }
147
- });
148
- }
149
- }
150
-
151
- // Getters
152
- get currentAnimation(): MascotAnimation | null {
153
- return this._currentAnimation;
154
- }
155
-
156
- get speed(): number {
157
- return this._speed;
60
+ this._events.off(event, callback);
158
61
  }
159
62
 
160
- get isPaused(): boolean {
161
- return this._isPaused;
63
+ destroy(): void {
64
+ this._player.destroy();
65
+ this._events.clear();
162
66
  }
163
67
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Animation Player (100 lines)
3
+ * Core playback logic for mascot animations
4
+ */
5
+
6
+ import type { MascotAnimation } from '../../domain/types/MascotTypes';
7
+ import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
8
+ import { AnimationTimer } from './AnimationTimer';
9
+
10
+ export class AnimationPlayer {
11
+ private _currentAnimation: MascotAnimation | null = null;
12
+ private _isPlaying: boolean = false;
13
+ private _isPaused: boolean = false;
14
+ private _progress: number = 0;
15
+ private _speed: number = 1;
16
+ private readonly _timer: AnimationTimer;
17
+
18
+ constructor() {
19
+ this._timer = new AnimationTimer();
20
+ }
21
+
22
+ get currentAnimation(): MascotAnimation | null {
23
+ return this._currentAnimation;
24
+ }
25
+
26
+ get isPlaying(): boolean {
27
+ return this._isPlaying && !this._isPaused;
28
+ }
29
+
30
+ get progress(): number {
31
+ return this._progress;
32
+ }
33
+
34
+ get speed(): number {
35
+ return this._speed;
36
+ }
37
+
38
+ play(animation: MascotAnimation, options?: AnimationOptions): Promise<void> {
39
+ // Stop existing animation
40
+ this.stop();
41
+
42
+ this._currentAnimation = animation;
43
+ this._isPlaying = true;
44
+ this._isPaused = false;
45
+
46
+ if (options?.speed !== undefined) {
47
+ this._speed = options.speed;
48
+ }
49
+
50
+ const duration = animation.duration || 2000;
51
+ const adjustedDuration = duration / this._speed;
52
+
53
+ // Start timer
54
+ this._timer.start(adjustedDuration, () => {
55
+ this._isPlaying = false;
56
+ this._progress = 1;
57
+ options?.onFinish?.();
58
+ });
59
+
60
+ options?.onStart?.();
61
+ return Promise.resolve();
62
+ }
63
+
64
+ pause(): void {
65
+ if (!this._isPlaying || this._isPaused) return;
66
+
67
+ this._isPaused = true;
68
+ this._timer.pause();
69
+ }
70
+
71
+ resume(): void {
72
+ if (!this._isPaused) return;
73
+
74
+ this._isPaused = false;
75
+ this._timer.resume();
76
+ }
77
+
78
+ stop(): void {
79
+ this._isPlaying = false;
80
+ this._isPaused = false;
81
+ this._progress = 0;
82
+ this._currentAnimation = null;
83
+ this._timer.stop();
84
+ }
85
+
86
+ setProgress(progress: number): void {
87
+ if (progress < 0 || progress > 1) {
88
+ throw new Error('Progress must be between 0 and 1');
89
+ }
90
+ this._progress = progress;
91
+ }
92
+
93
+ setSpeed(speed: number): void {
94
+ if (speed <= 0) {
95
+ throw new Error('Speed must be greater than 0');
96
+ }
97
+ this._speed = speed;
98
+ }
99
+
100
+ destroy(): void {
101
+ this.stop();
102
+ this._timer.destroy();
103
+ }
104
+ }