@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-mascot",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Interactive mascot system for React Native apps - Customizable animated characters with Lottie and SVG support, mood system, and easy integration",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Data Transfer Objects
3
+ * Used for data transfer between layers
4
+ */
5
+
6
+ import type { MascotConfig, MascotState, MascotAppearance, MascotPersonality } from '../../domain/types/MascotTypes';
7
+
8
+ /**
9
+ * Mascot DTO - simplified mascot data for presentation
10
+ */
11
+ export interface MascotDTO {
12
+ id: string;
13
+ name: string;
14
+ type: string;
15
+ personality: MascotPersonality;
16
+ appearance: MascotAppearance;
17
+ state: MascotState;
18
+ interactive: boolean;
19
+ touchEnabled: boolean;
20
+ soundEnabled: boolean;
21
+ }
22
+
23
+ /**
24
+ * Animation State DTO
25
+ */
26
+ export interface AnimationStateDTO {
27
+ isPlaying: boolean;
28
+ currentAnimation: string | null;
29
+ progress: number;
30
+ speed: number;
31
+ }
32
+
33
+ /**
34
+ * Mascot Initialization Options DTO
35
+ */
36
+ export interface MascotInitOptionsDTO {
37
+ config?: MascotConfig;
38
+ template?: string;
39
+ autoInitialize?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Animation Playback Options DTO
44
+ */
45
+ export interface AnimationPlaybackOptionsDTO {
46
+ speed?: number;
47
+ loop?: boolean;
48
+ autoplay?: boolean;
49
+ onStart?: () => void;
50
+ onFinish?: () => void;
51
+ onError?: (error: Error) => void;
52
+ }
53
+
54
+ /**
55
+ * Mascot Update Options DTO
56
+ */
57
+ export interface MascotUpdateOptionsDTO {
58
+ mood?: string;
59
+ energy?: number;
60
+ friendliness?: number;
61
+ playfulness?: number;
62
+ baseColor?: string;
63
+ accentColor?: string;
64
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Domain Errors
3
+ * Custom error classes for mascot domain
4
+ */
5
+
6
+ export class MascotError extends Error {
7
+ constructor(message: string) {
8
+ super(message);
9
+ this.name = 'MascotError';
10
+ Object.setPrototypeOf(this, MascotError.prototype);
11
+ }
12
+ }
13
+
14
+ export class MascotNotInitializedError extends MascotError {
15
+ constructor() {
16
+ super('Mascot has not been initialized. Call initialize() or fromTemplate() first.');
17
+ this.name = 'MascotNotInitializedError';
18
+ Object.setPrototypeOf(this, MascotNotInitializedError.prototype);
19
+ }
20
+ }
21
+
22
+ export class AnimationNotFoundError extends MascotError {
23
+ constructor(animationId: string) {
24
+ super(`Animation with id "${animationId}" not found.`);
25
+ this.name = 'AnimationNotFoundError';
26
+ Object.setPrototypeOf(this, AnimationNotFoundError.prototype);
27
+ }
28
+ }
29
+
30
+ export class InvalidEnergyLevelError extends MascotError {
31
+ constructor(value: number) {
32
+ super(`Energy level must be between 0 and 1, got: ${value}`);
33
+ this.name = 'InvalidEnergyLevelError';
34
+ Object.setPrototypeOf(this, InvalidEnergyLevelError.prototype);
35
+ }
36
+ }
37
+
38
+ export class InvalidFriendlinessLevelError extends MascotError {
39
+ constructor(value: number) {
40
+ super(`Friendliness level must be between 0 and 1, got: ${value}`);
41
+ this.name = 'InvalidFriendlinessLevelError';
42
+ Object.setPrototypeOf(this, InvalidFriendlinessLevelError.prototype);
43
+ }
44
+ }
45
+
46
+ export class InvalidPlayfulnessLevelError extends MascotError {
47
+ constructor(value: number) {
48
+ super(`Playfulness level must be between 0 and 1, got: ${value}`);
49
+ this.name = 'InvalidPlayfulnessLevelError';
50
+ Object.setPrototypeOf(this, InvalidPlayfulnessLevelError.prototype);
51
+ }
52
+ }
53
+
54
+ export class InvalidMoodTransitionError extends MascotError {
55
+ constructor(from: string, to: string) {
56
+ super(`Cannot transition mood from "${from}" to "${to}".`);
57
+ this.name = 'InvalidMoodTransitionError';
58
+ Object.setPrototypeOf(this, InvalidMoodTransitionError.prototype);
59
+ }
60
+ }
61
+
62
+ export class MascotNotFoundError extends MascotError {
63
+ constructor(id: string) {
64
+ super(`Mascot with id "${id}" not found.`);
65
+ this.name = 'MascotNotFoundError';
66
+ Object.setPrototypeOf(this, MascotNotFoundError.prototype);
67
+ }
68
+ }
69
+
70
+ export class TemplateNotFoundError extends MascotError {
71
+ constructor(template: string) {
72
+ super(`Mascot template "${template}" not found.`);
73
+ this.name = 'TemplateNotFoundError';
74
+ Object.setPrototypeOf(this, TemplateNotFoundError.prototype);
75
+ }
76
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Application Layer
3
+ * Exports all application layer components
4
+ */
5
+
6
+ export * from './errors/MascotErrors';
7
+ export * from './dto/MascotDTO';
8
+ export * from './services/MascotService';
@@ -0,0 +1,194 @@
1
+ /**
2
+ * MascotService
3
+ * Application service that orchestrates mascot use cases
4
+ * This is the main entry point for all mascot operations
5
+ */
6
+
7
+ import { Mascot } from '../../domain/entities/Mascot';
8
+ import type { MascotConfig, MascotMood, MascotAppearance, MascotAccessory } from '../../domain/types/MascotTypes';
9
+ import type { IMascotRepository } from '../../domain/interfaces/IMascotRepository';
10
+ import type { IAnimationController, AnimationOptions } from '../../domain/interfaces/IAnimationController';
11
+ import type { IAssetManager } from '../../domain/interfaces/IAssetManager';
12
+ import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
13
+ import { MascotNotInitializedError, AnimationNotFoundError } from '../errors/MascotErrors';
14
+
15
+ export type MascotTemplate = 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero';
16
+
17
+ export class MascotService {
18
+ private _mascot: Mascot | null = null;
19
+ private _changeListeners: Set<() => void> = new Set();
20
+
21
+ constructor(
22
+ private readonly _repository: IMascotRepository,
23
+ private readonly _animationController: IAnimationController,
24
+ private readonly _assetManager: IAssetManager
25
+ ) {}
26
+
27
+ // ✅ Initialization
28
+ async initialize(config: MascotConfig): Promise<void> {
29
+ this._mascot = new Mascot(config);
30
+ await this._repository.save(config);
31
+ this._notifyChange();
32
+ }
33
+
34
+ async fromTemplate(
35
+ template: MascotTemplate,
36
+ customizations?: Partial<MascotConfig>
37
+ ): Promise<void> {
38
+ const mascot = MascotFactory.createFromTemplate(template, customizations);
39
+ this._mascot = mascot;
40
+ await this._repository.save(mascot.config);
41
+ this._notifyChange();
42
+ }
43
+
44
+ // ✅ State Getters
45
+ get mascot(): Mascot | null {
46
+ return this._mascot;
47
+ }
48
+
49
+ get isPlaying(): boolean {
50
+ return this._animationController.isPlaying();
51
+ }
52
+
53
+ get currentAnimation(): string | null {
54
+ return this._mascot?.state.currentAnimation ?? null;
55
+ }
56
+
57
+ get isReady(): boolean {
58
+ return this._mascot !== null;
59
+ }
60
+
61
+ // ✅ Personality Management
62
+ setMood(mood: MascotMood): void {
63
+ this._ensureMascot();
64
+ this._mascot!.setMood(mood);
65
+ this._notifyChange();
66
+ }
67
+
68
+ setEnergy(value: number): void {
69
+ this._ensureMascot();
70
+ this._mascot!.setEnergy(value);
71
+ this._notifyChange();
72
+ }
73
+
74
+ setFriendliness(value: number): void {
75
+ this._ensureMascot();
76
+ this._mascot!.setFriendliness(value);
77
+ this._notifyChange();
78
+ }
79
+
80
+ setPlayfulness(value: number): void {
81
+ this._ensureMascot();
82
+ this._mascot!.setPlayfulness(value);
83
+ this._notifyChange();
84
+ }
85
+
86
+ // ✅ Rich Behaviors
87
+ cheerUp(): void {
88
+ this._ensureMascot();
89
+ this._mascot!.cheerUp();
90
+ this._notifyChange();
91
+ }
92
+
93
+ boostEnergy(amount: number): void {
94
+ this._ensureMascot();
95
+ this._mascot!.boostEnergy(amount);
96
+ this._notifyChange();
97
+ }
98
+
99
+ // ✅ Appearance Management
100
+ updateAppearance(appearance: Partial<MascotAppearance>): void {
101
+ this._ensureMascot();
102
+ this._mascot!.updateAppearance(appearance);
103
+ this._notifyChange();
104
+ }
105
+
106
+ setBaseColor(color: string): void {
107
+ this._ensureMascot();
108
+ this._mascot!.setBaseColor(color);
109
+ this._notifyChange();
110
+ }
111
+
112
+ setAccentColor(color: string): void {
113
+ this._ensureMascot();
114
+ this._mascot!.setAccentColor(color);
115
+ this._notifyChange();
116
+ }
117
+
118
+ addAccessory(accessory: {
119
+ id: string;
120
+ type: string;
121
+ color?: string;
122
+ position?: { x: number; y: number };
123
+ }): void {
124
+ this._ensureMascot();
125
+ this._mascot!.addAccessory(accessory);
126
+ this._notifyChange();
127
+ }
128
+
129
+ removeAccessory(accessoryId: string): void {
130
+ this._ensureMascot();
131
+ this._mascot!.removeAccessory(accessoryId);
132
+ this._notifyChange();
133
+ }
134
+
135
+ // ✅ Animation Management
136
+ async playAnimation(animationId: string, options?: AnimationOptions): Promise<void> {
137
+ this._ensureMascot();
138
+ const animation = this._mascot!.getAnimation(animationId);
139
+ if (!animation) {
140
+ throw new AnimationNotFoundError(animationId);
141
+ }
142
+ this._mascot!.startAnimation(animationId);
143
+ await this._animationController.play(animation, options);
144
+ this._notifyChange();
145
+ }
146
+
147
+ stopAnimation(): void {
148
+ this._animationController.stop();
149
+ if (this._mascot) {
150
+ this._mascot.stopAnimation();
151
+ }
152
+ this._notifyChange();
153
+ }
154
+
155
+ pauseAnimation(): void {
156
+ this._animationController.pause();
157
+ this._notifyChange();
158
+ }
159
+
160
+ resumeAnimation(): void {
161
+ this._animationController.resume();
162
+ this._notifyChange();
163
+ }
164
+
165
+ // ✅ Visibility & Position
166
+ setVisible(visible: boolean): void {
167
+ this._ensureMascot();
168
+ this._mascot!.setVisible(visible);
169
+ this._notifyChange();
170
+ }
171
+
172
+ setPosition(position: { x: number; y: number }): void {
173
+ this._ensureMascot();
174
+ this._mascot!.setPosition(position);
175
+ this._notifyChange();
176
+ }
177
+
178
+ // ✅ Observable Pattern for React Integration
179
+ subscribe(listener: () => void): () => void {
180
+ this._changeListeners.add(listener);
181
+ return () => this._changeListeners.delete(listener);
182
+ }
183
+
184
+ private _notifyChange(): void {
185
+ this._changeListeners.forEach((listener) => listener());
186
+ }
187
+
188
+ // ✅ Validation
189
+ private _ensureMascot(): void {
190
+ if (!this._mascot) {
191
+ throw new MascotNotInitializedError();
192
+ }
193
+ }
194
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Mascot Entity
3
- * Core mascot representation following DDD principles
3
+ * Core mascot representation following DDD principles with Value Objects
4
4
  */
5
5
 
6
6
  import type {
@@ -13,12 +13,16 @@ import type {
13
13
  MascotMood,
14
14
  MascotAccessory,
15
15
  } from '../types/MascotTypes';
16
+ import { Mood, EnergyLevel, FriendlinessLevel, PlayfulnessLevel } from '../value-objects';
16
17
 
17
18
  export class Mascot {
18
19
  readonly id: string;
19
20
  readonly name: string;
20
21
  readonly type: MascotType;
21
- private _personality: MascotPersonality;
22
+ private _mood: Mood;
23
+ private _energy: EnergyLevel;
24
+ private _friendliness: FriendlinessLevel;
25
+ private _playfulness: PlayfulnessLevel;
22
26
  private _appearance: MascotAppearance;
23
27
  private readonly _animations: Map<string, MascotAnimation>;
24
28
  private readonly _config: MascotConfig;
@@ -29,7 +33,10 @@ export class Mascot {
29
33
  this.id = config.id;
30
34
  this.name = config.name;
31
35
  this.type = config.type;
32
- this._personality = config.personality;
36
+ this._mood = Mood.create(config.personality.mood);
37
+ this._energy = EnergyLevel.create(config.personality.energy);
38
+ this._friendliness = FriendlinessLevel.create(config.personality.friendliness);
39
+ this._playfulness = PlayfulnessLevel.create(config.personality.playfulness);
33
40
  this._appearance = config.appearance;
34
41
  this._animations = new Map(
35
42
  config.animations.map((anim) => [anim.id, anim])
@@ -46,7 +53,12 @@ export class Mascot {
46
53
 
47
54
  // Getters
48
55
  get personality(): MascotPersonality {
49
- return { ...this._personality };
56
+ return {
57
+ mood: this._mood.value,
58
+ energy: this._energy.value,
59
+ friendliness: this._friendliness.value,
60
+ playfulness: this._playfulness.value,
61
+ };
50
62
  }
51
63
 
52
64
  get appearance(): MascotAppearance {
@@ -77,31 +89,58 @@ export class Mascot {
77
89
  return this._config.soundEnabled ?? false;
78
90
  }
79
91
 
80
- // Personality Management
92
+ // Value Object Getters (for domain logic)
93
+ get mood(): Mood {
94
+ return this._mood;
95
+ }
96
+
97
+ get energy(): EnergyLevel {
98
+ return this._energy;
99
+ }
100
+
101
+ get friendliness(): FriendlinessLevel {
102
+ return this._friendliness;
103
+ }
104
+
105
+ get playfulness(): PlayfulnessLevel {
106
+ return this._playfulness;
107
+ }
108
+
109
+ // Personality Management (using Value Objects)
81
110
  setMood(mood: MascotMood): void {
82
- this._personality.mood = mood;
111
+ this._mood = Mood.create(mood);
83
112
  this._state.currentMood = mood;
84
113
  }
85
114
 
86
- setEnergy(energy: number): void {
87
- if (energy < 0 || energy > 1) {
88
- throw new Error('Energy must be between 0 and 1');
89
- }
90
- this._personality.energy = energy;
115
+ setEnergy(value: number): void {
116
+ this._energy = EnergyLevel.create(value);
91
117
  }
92
118
 
93
- setFriendliness(friendliness: number): void {
94
- if (friendliness < 0 || friendliness > 1) {
95
- throw new Error('Friendliness must be between 0 and 1');
96
- }
97
- this._personality.friendliness = friendliness;
119
+ setFriendliness(value: number): void {
120
+ this._friendliness = FriendlinessLevel.create(value);
98
121
  }
99
122
 
100
- setPlayfulness(playfulness: number): void {
101
- if (playfulness < 0 || playfulness > 1) {
102
- throw new Error('Playfulness must be between 0 and 1');
123
+ setPlayfulness(value: number): void {
124
+ this._playfulness = PlayfulnessLevel.create(value);
125
+ }
126
+
127
+ // Rich behavior with Value Objects
128
+ cheerUp(): void {
129
+ if (this._mood.isNegative()) {
130
+ this._mood = Mood.create('neutral');
103
131
  }
104
- this._personality.playfulness = playfulness;
132
+ }
133
+
134
+ boostEnergy(amount: number): void {
135
+ this._energy = this._energy.increase(amount);
136
+ }
137
+
138
+ drainEnergy(amount: number): void {
139
+ this._energy = this._energy.decrease(amount);
140
+ }
141
+
142
+ makeMoreFriendly(amount: number): void {
143
+ this._friendliness = this._friendliness.increase(amount);
105
144
  }
106
145
 
107
146
  // Appearance Management
@@ -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
+ }