@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Machine (120 lines)
|
|
3
|
+
* Core state transition logic with validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
|
|
7
|
+
import { AnimationState } from '../../domain/value-objects/AnimationState';
|
|
8
|
+
import { StateTransitions } from './StateTransitions';
|
|
9
|
+
import { StateHistory } from './StateHistory';
|
|
10
|
+
|
|
11
|
+
export interface StateMachineConfig {
|
|
12
|
+
enableAutoTransition?: boolean;
|
|
13
|
+
enableHistoryTracking?: boolean;
|
|
14
|
+
maxHistorySize?: number;
|
|
15
|
+
onStateChange?: (from: MascotAnimationState, to: MascotAnimationState) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class StateMachine {
|
|
19
|
+
private _currentState: AnimationState;
|
|
20
|
+
private _previousState: AnimationState | null = null;
|
|
21
|
+
private readonly _transitions: StateTransitions;
|
|
22
|
+
private readonly _history: StateHistory;
|
|
23
|
+
private readonly _config: Required<StateMachineConfig>;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
initialState: MascotAnimationState = 'idle',
|
|
27
|
+
config: StateMachineConfig = {}
|
|
28
|
+
) {
|
|
29
|
+
this._currentState = AnimationState.create(initialState);
|
|
30
|
+
this._transitions = new StateTransitions();
|
|
31
|
+
this._history = new StateHistory(config.maxHistorySize || 50);
|
|
32
|
+
this._config = {
|
|
33
|
+
enableAutoTransition: config.enableAutoTransition ?? true,
|
|
34
|
+
enableHistoryTracking: config.enableHistoryTracking ?? true,
|
|
35
|
+
maxHistorySize: config.maxHistorySize ?? 50,
|
|
36
|
+
onStateChange: config.onStateChange ?? (() => {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get currentState(): AnimationState {
|
|
41
|
+
return this._currentState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get currentStateValue(): MascotAnimationState {
|
|
45
|
+
return this._currentState.value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get previousState(): AnimationState | null {
|
|
49
|
+
return this._previousState;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get history(): MascotAnimationState[] {
|
|
53
|
+
return this._history.getStates();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
transitionTo(
|
|
57
|
+
newState: MascotAnimationState,
|
|
58
|
+
triggeredBy: 'user' | 'system' | 'auto-transition' = 'user'
|
|
59
|
+
): void {
|
|
60
|
+
// Validate transition
|
|
61
|
+
if (!this._transitions.canTransitionTo(this.currentStateValue, newState)) {
|
|
62
|
+
const validTransitions = this._transitions
|
|
63
|
+
.getAvailableTransitions(this.currentStateValue)
|
|
64
|
+
.map((t) => t.to);
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Cannot transition from '${this.currentStateValue}' to '${newState}'. ` +
|
|
67
|
+
`Valid transitions: ${validTransitions.join(', ')}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Store previous state
|
|
72
|
+
this._previousState = this._currentState;
|
|
73
|
+
|
|
74
|
+
// Track history
|
|
75
|
+
if (this._config.enableHistoryTracking) {
|
|
76
|
+
this._history.add(this.currentStateValue, triggeredBy);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Update state
|
|
80
|
+
const oldState = this.currentStateValue;
|
|
81
|
+
this._currentState = AnimationState.create(newState);
|
|
82
|
+
|
|
83
|
+
// Notify
|
|
84
|
+
this._config.onStateChange(oldState, newState);
|
|
85
|
+
|
|
86
|
+
// Schedule auto-transition
|
|
87
|
+
if (this._config.enableAutoTransition && !this._currentState.shouldLoop()) {
|
|
88
|
+
const next = this._currentState.getNextState();
|
|
89
|
+
if (next) {
|
|
90
|
+
this._scheduleAutoTransition(next);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Shortcut methods
|
|
96
|
+
triggerSuccess(): void {
|
|
97
|
+
this.transitionTo('success', 'system');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
triggerError(): void {
|
|
101
|
+
this.transitionTo('error', 'system');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
startLoading(): void {
|
|
105
|
+
this.transitionTo('loading', 'system');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
stopLoading(success: boolean): void {
|
|
109
|
+
if (this.currentStateValue === 'loading') {
|
|
110
|
+
this.transitionTo(success ? 'success' : 'error', 'system');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
reset(): void {
|
|
115
|
+
this.transitionTo('idle', 'system');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// State queries
|
|
119
|
+
isLooping(): boolean {
|
|
120
|
+
return this._currentState.shouldLoop();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isIdle(): boolean {
|
|
124
|
+
return this.currentStateValue === 'idle';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
isLoading(): boolean {
|
|
128
|
+
return this.currentStateValue === 'loading';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Auto-transition scheduling
|
|
132
|
+
private _autoTransitionTimer: NodeJS.Timeout | null = null;
|
|
133
|
+
|
|
134
|
+
private _scheduleAutoTransition(targetState: MascotAnimationState): void {
|
|
135
|
+
const duration = this._currentState.getDuration();
|
|
136
|
+
const delay = this._currentState.getTransitionDelay(targetState);
|
|
137
|
+
|
|
138
|
+
this._autoTransitionTimer = setTimeout(() => {
|
|
139
|
+
try {
|
|
140
|
+
this.transitionTo(targetState, 'auto-transition');
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn('Auto-transition failed:', error);
|
|
143
|
+
}
|
|
144
|
+
}, duration + delay);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
destroy(): void {
|
|
148
|
+
if (this._autoTransitionTimer) {
|
|
149
|
+
clearTimeout(this._autoTransitionTimer);
|
|
150
|
+
}
|
|
151
|
+
this._history.clear();
|
|
152
|
+
this._config.onStateChange = () => {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Transitions (80 lines)
|
|
3
|
+
* Transition rules and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { MascotAnimationState } from '../../domain/types/AnimationStateTypes';
|
|
7
|
+
|
|
8
|
+
export interface StateTransition {
|
|
9
|
+
from: MascotAnimationState;
|
|
10
|
+
to: MascotAnimationState;
|
|
11
|
+
condition?: 'always' | 'on-success' | 'on-error' | 'on-complete';
|
|
12
|
+
delay?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class StateTransitions {
|
|
16
|
+
private readonly _transitions: Map<MascotAnimationState, StateTransition[]>;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this._transitions = new Map();
|
|
20
|
+
this._initializeTransitions();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
canTransitionTo(from: MascotAnimationState, to: MascotAnimationState): boolean {
|
|
24
|
+
const transitions = this._transitions.get(from);
|
|
25
|
+
if (!transitions) return false;
|
|
26
|
+
return transitions.some((t) => t.to === to);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getAvailableTransitions(state: MascotAnimationState): StateTransition[] {
|
|
30
|
+
return this._transitions.get(state) || [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getTransitionDelay(from: MascotAnimationState, to: MascotAnimationState): number {
|
|
34
|
+
const transitions = this._transitions.get(from);
|
|
35
|
+
const transition = transitions?.find((t) => t.to === to);
|
|
36
|
+
return transition?.delay || 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private _initializeTransitions(): void {
|
|
40
|
+
// Idle transitions
|
|
41
|
+
this._addTransition('idle', 'loading', 'always');
|
|
42
|
+
this._addTransition('idle', 'empty', 'always');
|
|
43
|
+
this._addTransition('idle', 'guide', 'always');
|
|
44
|
+
|
|
45
|
+
// Loading transitions
|
|
46
|
+
this._addTransition('loading', 'success', 'on-success');
|
|
47
|
+
this._addTransition('loading', 'error', 'on-error');
|
|
48
|
+
|
|
49
|
+
// Success transitions
|
|
50
|
+
this._addTransition('success', 'idle', 'on-complete', 100);
|
|
51
|
+
|
|
52
|
+
// Error transitions
|
|
53
|
+
this._addTransition('error', 'idle', 'on-complete', 100);
|
|
54
|
+
|
|
55
|
+
// Empty transitions
|
|
56
|
+
this._addTransition('empty', 'idle', 'always');
|
|
57
|
+
|
|
58
|
+
// Guide transitions
|
|
59
|
+
this._addTransition('guide', 'idle', 'on-complete', 200);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _addTransition(
|
|
63
|
+
from: MascotAnimationState,
|
|
64
|
+
to: MascotAnimationState,
|
|
65
|
+
condition: StateTransition['condition'] = 'always',
|
|
66
|
+
delay: number = 0
|
|
67
|
+
): void {
|
|
68
|
+
if (!this._transitions.has(from)) {
|
|
69
|
+
this._transitions.set(from, []);
|
|
70
|
+
}
|
|
71
|
+
this._transitions.get(from)!.push({ from, to, condition, delay });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Layer Exports (60 lines)
|
|
3
|
+
* Services, use cases, and DTOs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Application - Services
|
|
7
|
+
export { AnimationStateManager } from './application/services/AnimationStateManager';
|
|
8
|
+
export type { AnimationStateManagerConfig } from './application/services/AnimationStateManager';
|
|
9
|
+
|
|
10
|
+
export { MascotService } from './application/services/MascotService';
|
|
11
|
+
export type { MascotTemplate } from './application/services/MascotService';
|
|
12
|
+
|
|
13
|
+
export { PersonalityManagement } from './application/services/PersonalityManagement';
|
|
14
|
+
export { AppearanceManagement } from './application/services/AppearanceManagement';
|
|
15
|
+
|
|
16
|
+
export { StateMachine } from './application/services/StateMachine';
|
|
17
|
+
export { StateTransitions } from './application/services/StateTransitions';
|
|
18
|
+
export { StateHistory } from './application/services/StateHistory';
|
|
19
|
+
|
|
20
|
+
// Application - Errors
|
|
21
|
+
export {
|
|
22
|
+
MascotError,
|
|
23
|
+
MascotNotInitializedError,
|
|
24
|
+
AnimationNotFoundError,
|
|
25
|
+
InvalidEnergyLevelError,
|
|
26
|
+
InvalidFriendlinessLevelError,
|
|
27
|
+
InvalidPlayfulnessLevelError,
|
|
28
|
+
InvalidMoodTransitionError,
|
|
29
|
+
MascotNotFoundError,
|
|
30
|
+
TemplateNotFoundError,
|
|
31
|
+
} from './application/errors/MascotErrors';
|
|
32
|
+
|
|
33
|
+
// Application - DTOs
|
|
34
|
+
export type {
|
|
35
|
+
MascotDTO,
|
|
36
|
+
AnimationStateDTO,
|
|
37
|
+
MascotInitOptionsDTO,
|
|
38
|
+
AnimationPlaybackOptionsDTO,
|
|
39
|
+
MascotUpdateOptionsDTO,
|
|
40
|
+
} from './application/dto/MascotDTO';
|
package/src/assets/index.ts
CHANGED
|
@@ -3,21 +3,16 @@
|
|
|
3
3
|
* Pre-configured mascots and animations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
7
6
|
import type { MascotConfig, MascotAnimationType } from '../domain/types/MascotTypes';
|
|
7
|
+
import type { MascotAnimation } from '../domain/types/MascotTypes';
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const successAnim = require('./lottie/success.json');
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
18
|
-
const errorAnim = require('./lottie/error.json');
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
-
const danceAnim = require('./lottie/dance.json');
|
|
9
|
+
// Import JSON animations
|
|
10
|
+
import idleAnim from './lottie/idle.json';
|
|
11
|
+
import waveAnim from './lottie/wave.json';
|
|
12
|
+
import jumpAnim from './lottie/jump.json';
|
|
13
|
+
import successAnim from './lottie/success.json';
|
|
14
|
+
import errorAnim from './lottie/error.json';
|
|
15
|
+
import danceAnim from './lottie/dance.json';
|
|
21
16
|
|
|
22
17
|
export const BUILT_IN_MASCOTS: Record<string, MascotConfig> = {
|
|
23
18
|
'happy-robot': {
|
|
@@ -42,7 +37,7 @@ export const BUILT_IN_MASCOTS: Record<string, MascotConfig> = {
|
|
|
42
37
|
id: 'idle',
|
|
43
38
|
name: 'Idle',
|
|
44
39
|
type: 'idle' as MascotAnimationType,
|
|
45
|
-
source: idleAnim,
|
|
40
|
+
source: idleAnim as MascotAnimation['source'],
|
|
46
41
|
loop: true,
|
|
47
42
|
autoplay: true,
|
|
48
43
|
},
|
|
@@ -50,35 +45,35 @@ export const BUILT_IN_MASCOTS: Record<string, MascotConfig> = {
|
|
|
50
45
|
id: 'wave',
|
|
51
46
|
name: 'Wave',
|
|
52
47
|
type: 'action' as MascotAnimationType,
|
|
53
|
-
source: waveAnim,
|
|
48
|
+
source: waveAnim as MascotAnimation['source'],
|
|
54
49
|
loop: false,
|
|
55
50
|
},
|
|
56
51
|
{
|
|
57
52
|
id: 'jump',
|
|
58
53
|
name: 'Jump',
|
|
59
54
|
type: 'action' as MascotAnimationType,
|
|
60
|
-
source: jumpAnim,
|
|
55
|
+
source: jumpAnim as MascotAnimation['source'],
|
|
61
56
|
loop: false,
|
|
62
57
|
},
|
|
63
58
|
{
|
|
64
59
|
id: 'success',
|
|
65
60
|
name: 'Success',
|
|
66
61
|
type: 'reaction' as MascotAnimationType,
|
|
67
|
-
source: successAnim,
|
|
62
|
+
source: successAnim as MascotAnimation['source'],
|
|
68
63
|
loop: false,
|
|
69
64
|
},
|
|
70
65
|
{
|
|
71
66
|
id: 'error',
|
|
72
67
|
name: 'Error',
|
|
73
68
|
type: 'reaction' as MascotAnimationType,
|
|
74
|
-
source: errorAnim,
|
|
69
|
+
source: errorAnim as MascotAnimation['source'],
|
|
75
70
|
loop: false,
|
|
76
71
|
},
|
|
77
72
|
{
|
|
78
73
|
id: 'dance',
|
|
79
74
|
name: 'Dance',
|
|
80
75
|
type: 'action' as MascotAnimationType,
|
|
81
|
-
source: danceAnim,
|
|
76
|
+
source: danceAnim as MascotAnimation['source'],
|
|
82
77
|
loop: true,
|
|
83
78
|
},
|
|
84
79
|
],
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Domain Exports (80 lines)
|
|
3
|
+
* Domain entities, value objects, and types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Domain - Entities
|
|
7
|
+
export { Mascot } from './domain/entities/Mascot';
|
|
8
|
+
|
|
9
|
+
// Domain - Value Objects
|
|
10
|
+
export { Mood } from './domain/value-objects/Mood';
|
|
11
|
+
export { EnergyLevel } from './domain/value-objects/EnergyLevel';
|
|
12
|
+
export { FriendlinessLevel } from './domain/value-objects/FriendlinessLevel';
|
|
13
|
+
export { PlayfulnessLevel } from './domain/value-objects/PlayfulnessLevel';
|
|
14
|
+
export { AnimationState } from './domain/value-objects/AnimationState';
|
|
15
|
+
|
|
16
|
+
// Domain - Types - Animation States
|
|
17
|
+
export type {
|
|
18
|
+
MascotAnimationState,
|
|
19
|
+
MascotStateConfig,
|
|
20
|
+
StateTransition,
|
|
21
|
+
StateHistoryEntry,
|
|
22
|
+
MascotSize,
|
|
23
|
+
MascotSizeConfig,
|
|
24
|
+
} from './domain/types/AnimationStateTypes';
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
DEFAULT_STATE_CONFIGS,
|
|
28
|
+
DEFAULT_SIZE_CONFIG,
|
|
29
|
+
STATE_TRANSITIONS,
|
|
30
|
+
} from './domain/types/AnimationStateTypes';
|
|
31
|
+
|
|
32
|
+
// Domain - Types - Mascot
|
|
33
|
+
export type {
|
|
34
|
+
MascotType,
|
|
35
|
+
MascotStyle,
|
|
36
|
+
MascotAnimationType,
|
|
37
|
+
MascotAppearance,
|
|
38
|
+
MascotAccessory,
|
|
39
|
+
MascotPersonality,
|
|
40
|
+
MascotAnimation,
|
|
41
|
+
MascotConfig,
|
|
42
|
+
MascotState,
|
|
43
|
+
MascotInteraction,
|
|
44
|
+
MascotMood,
|
|
45
|
+
AnimationSpeed,
|
|
46
|
+
} from './domain/types/MascotTypes';
|
|
47
|
+
|
|
48
|
+
// Domain - Interfaces
|
|
49
|
+
export type {
|
|
50
|
+
IAnimationController,
|
|
51
|
+
AnimationEvent,
|
|
52
|
+
AnimationOptions,
|
|
53
|
+
} from './domain/interfaces/IAnimationController';
|
|
54
|
+
|
|
55
|
+
export type {
|
|
56
|
+
IAssetManager,
|
|
57
|
+
AssetCache,
|
|
58
|
+
} from './domain/interfaces/IAssetManager';
|
|
59
|
+
|
|
60
|
+
export type {
|
|
61
|
+
IMascotRepository,
|
|
62
|
+
} from './domain/interfaces/IMascotRepository';
|