Skip to content

Commit 81c2b2d

Browse files
committed
feat: function api
1 parent ffe18c8 commit 81c2b2d

5 files changed

Lines changed: 163 additions & 108 deletions

File tree

example/src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ export default function App() {
2222
setTheme(theme === 'light' ? 'dark' : 'light');
2323
},
2424
animationConfig: {
25-
type: 'fade',
25+
type: 'circular',
2626
duration: 900,
27+
startingPoint: {
28+
cx: 200,
29+
cy: 0,
30+
},
2731
},
2832
});
2933
}}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NativeModules, NativeEventEmitter } from 'react-native';
2+
3+
export default class ThemeSwitchAnimationListener {
4+
private listenerAdded: boolean;
5+
private eventEmitter: NativeEventEmitter;
6+
7+
constructor() {
8+
this.listenerAdded = false;
9+
this.eventEmitter = new NativeEventEmitter(
10+
NativeModules.ThemeSwitchAnimationModule
11+
);
12+
}
13+
14+
addEventListener(callback: () => void) {
15+
if (!this.listenerAdded) {
16+
this.eventEmitter.addListener('FINISHED_FREEZING_SCREEN', callback);
17+
this.listenerAdded = true;
18+
}
19+
}
20+
}

src/helpers.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export const validateCoordinates = (
2+
value: number,
3+
max: number,
4+
name: string
5+
) => {
6+
if (value === undefined) {
7+
console.warn(`${name} is undefined. Please provide both cx and cy.`);
8+
return false;
9+
}
10+
if (value > max) {
11+
console.warn(
12+
`${name} is greater than ${max}. Please provide a ${name} smaller than screen size.`
13+
);
14+
return false;
15+
}
16+
if (value < -max) {
17+
console.warn(
18+
`${name} is smaller than -${max}. Please provide a ${name} bigger than -screen size.`
19+
);
20+
return false;
21+
}
22+
return true;
23+
};
24+
25+
export const calculateRatio = (value: number, max: number) => {
26+
return value >= 0 ? value / max : 1 + value / max;
27+
};
28+
29+
export const calculateActualRation = (ration: number) => {
30+
return ration > 0 ? ration : 1 + ration;
31+
};

src/index.tsx

Lines changed: 73 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
import { NativeModules, Platform, Dimensions } from 'react-native';
2+
import type {
3+
AnimationConfig,
4+
CircularAnimationConfig,
5+
CircularAnimationConfigExact,
6+
ThemeSwitcherHookProps,
7+
} from './types';
8+
import ThemeSwitchAnimationListener from './ThemeSwitchAnimationListener';
19
import {
2-
NativeModules,
3-
Platform,
4-
NativeEventEmitter,
5-
Dimensions,
6-
} from 'react-native';
10+
calculateActualRation,
11+
calculateRatio,
12+
validateCoordinates,
13+
} from './helpers';
714

815
const { width, height } = Dimensions.get('screen');
916

@@ -24,88 +31,32 @@ const ThemeSwitchAnimation = NativeModules.ThemeSwitchAnimationModule
2431
}
2532
);
2633

27-
type CircularAnimationType = 'circular' | 'inverted-circular';
28-
29-
type CircularAnimationConfig = {
30-
type: CircularAnimationType;
31-
duration: number;
32-
cxRatio: number;
33-
cyRatio: number;
34-
};
35-
36-
type CircularAnimationConfigExact = {
37-
type: CircularAnimationType;
38-
duration: number;
39-
cx: number;
40-
cy: number;
41-
};
42-
43-
type FadeAnimationConfig = {
44-
type: 'fade';
45-
duration: number;
46-
};
47-
48-
type AnimationConfig =
49-
| CircularAnimationConfig
50-
| FadeAnimationConfig
51-
| CircularAnimationConfigExact;
52-
53-
type ThemeSwitcherHookProps = {
54-
switchThemeFunction: () => void;
55-
animationConfig?: AnimationConfig;
56-
};
57-
5834
let switchFunction: () => void = () => {};
5935
let localAnimationConfig: AnimationConfig = {
6036
type: 'fade',
6137
duration: 500,
6238
};
6339

64-
new NativeEventEmitter(NativeModules.ThemeSwitchAnimationModule).addListener(
65-
'FINISHED_FREEZING_SCREEN',
66-
() => {
67-
setTimeout(() => {
68-
if (switchFunction) {
69-
switchFunction();
70-
if (localAnimationConfig) {
71-
unfreezeWrapper();
72-
}
40+
const themeSwitchAnimationListener = new ThemeSwitchAnimationListener();
41+
42+
themeSwitchAnimationListener.addEventListener(() => {
43+
setTimeout(() => {
44+
if (switchFunction) {
45+
switchFunction();
46+
if (localAnimationConfig) {
47+
unfreezeWrapper();
7348
}
74-
});
75-
}
76-
);
49+
}
50+
});
51+
});
7752

7853
const switchTheme = ({
79-
switchThemeFunction: incomingSwithThemeFunction,
54+
switchThemeFunction: incomingSwitchThemeFunction,
8055
animationConfig,
8156
}: ThemeSwitcherHookProps) => {
8257
localAnimationConfig = animationConfig || localAnimationConfig;
8358
ThemeSwitchAnimation.freezeScreen();
84-
switchFunction = incomingSwithThemeFunction;
85-
};
86-
87-
const validateCoordinates = (value: number, max: number, name: string) => {
88-
if (value === undefined) {
89-
throw new Error(`${name} is undefined. Please provide both cx and cy.`);
90-
}
91-
if (value > max) {
92-
throw new Error(
93-
`${name} is greater than ${max}. Please provide a ${name} smaller than screen size.`
94-
);
95-
}
96-
if (value < -max) {
97-
throw new Error(
98-
`${name} is smaller than -${max}. Please provide a ${name} bigger than -screen size.`
99-
);
100-
}
101-
};
102-
103-
const calculateRatio = (value: number, max: number) => {
104-
return value > 0 ? value / max : 1 + value / max;
105-
};
106-
107-
const calculateActualRation = (ration: number) => {
108-
return ration > 0 ? ration : 1 + ration;
59+
switchFunction = incomingSwitchThemeFunction;
10960
};
11061

11162
const unfreezeWrapper = () => {
@@ -115,41 +66,56 @@ const unfreezeWrapper = () => {
11566
localAnimationConfig.type === 'circular' ||
11667
localAnimationConfig.type === 'inverted-circular'
11768
) {
118-
if ('cx' in localAnimationConfig && 'cy' in localAnimationConfig) {
119-
const { cx, cy } = localAnimationConfig as CircularAnimationConfigExact;
120-
121-
validateCoordinates(cx, width, 'cx');
122-
validateCoordinates(cy, height, 'cy');
123-
124-
const cxRatio = calculateRatio(cx, width);
125-
const cyRatio = calculateRatio(cy, height);
126-
127-
ThemeSwitchAnimation.unfreezeScreen(
128-
localAnimationConfig.type,
129-
localAnimationConfig.duration,
130-
cxRatio,
131-
cyRatio
132-
);
69+
if (
70+
'cx' in localAnimationConfig.startingPoint &&
71+
'cy' in localAnimationConfig.startingPoint
72+
) {
73+
const { cx, cy } = (
74+
localAnimationConfig as CircularAnimationConfigExact
75+
)?.startingPoint;
76+
77+
if (
78+
validateCoordinates(cx, width, 'cx') &&
79+
validateCoordinates(cy, height, 'cy')
80+
) {
81+
const cxRatio = calculateRatio(cx, width);
82+
const cyRatio = calculateRatio(cy, height);
83+
84+
ThemeSwitchAnimation.unfreezeScreen(
85+
localAnimationConfig.type,
86+
localAnimationConfig.duration,
87+
cxRatio,
88+
cyRatio
89+
);
90+
} else {
91+
// cleanup
92+
ThemeSwitchAnimation.unfreezeScreen('fade', 200, 0.5, 0.5);
93+
}
13394
} else if (
134-
'cxRatio' in localAnimationConfig &&
135-
'cyRatio' in localAnimationConfig
95+
'cxRatio' in localAnimationConfig.startingPoint &&
96+
'cyRatio' in localAnimationConfig.startingPoint
13697
) {
137-
// Assuming 'cxRatio' and 'cyRatio' are part of CircularAnimationConfig
138-
const { cxRatio, cyRatio } =
139-
localAnimationConfig as CircularAnimationConfig;
140-
141-
validateCoordinates(cxRatio, 1, 'cxRatio');
142-
validateCoordinates(cyRatio, 1, 'cyRatio');
143-
144-
const cxRatioActual = calculateActualRation(cxRatio);
145-
const cyRatioActual = calculateActualRation(cyRatio);
146-
147-
ThemeSwitchAnimation.unfreezeScreen(
148-
localAnimationConfig.type,
149-
localAnimationConfig.duration,
150-
cxRatioActual,
151-
cyRatioActual
152-
);
98+
const { cxRatio, cyRatio } = (
99+
localAnimationConfig as CircularAnimationConfig
100+
)?.startingPoint;
101+
102+
if (
103+
validateCoordinates(cxRatio, 1, 'cxRatio') &&
104+
validateCoordinates(cyRatio, 1, 'cyRatio')
105+
) {
106+
const cxRatioActual = calculateActualRation(cxRatio);
107+
const cyRatioActual = calculateActualRation(cyRatio);
108+
109+
ThemeSwitchAnimation.unfreezeScreen(
110+
localAnimationConfig.type,
111+
localAnimationConfig.duration,
112+
cxRatioActual,
113+
cyRatioActual
114+
);
115+
} else {
116+
// cleanup
117+
ThemeSwitchAnimation.unfreezeScreen('fade', 500, 0.5, 0.5);
118+
}
153119
}
154120
} else {
155121
ThemeSwitchAnimation.unfreezeScreen(

src/types.d.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
type CircularAnimationType = 'circular' | 'inverted-circular';
2+
3+
export type CircularAnimationConfig = {
4+
type: CircularAnimationType;
5+
duration: number;
6+
startingPoint: {
7+
cxRatio: number;
8+
cyRatio: number;
9+
};
10+
};
11+
12+
export type CircularAnimationConfigExact = {
13+
type: CircularAnimationType;
14+
duration: number;
15+
startingPoint: {
16+
cx: number;
17+
cy: number;
18+
};
19+
};
20+
21+
export type FadeAnimationConfig = {
22+
type: 'fade';
23+
duration: number;
24+
};
25+
26+
export type AnimationConfig =
27+
| CircularAnimationConfig
28+
| FadeAnimationConfig
29+
| CircularAnimationConfigExact;
30+
31+
export type ThemeSwitcherHookProps = {
32+
switchThemeFunction: () => void;
33+
animationConfig?: AnimationConfig;
34+
};

0 commit comments

Comments
 (0)