Skip to content

Commit 4f9f68c

Browse files
authored
Merge pull request #6 from kingstinct/add-config-plugins
added confing plugins to make it easier to the users
2 parents cabd9bc + cc6c354 commit 4f9f68c

File tree

10 files changed

+293
-11
lines changed

10 files changed

+293
-11
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ jobs:
2828

2929
- name: Run typecheck
3030
run: npm run typecheck
31+
32+
- name: Run tests
33+
run: npm test

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) [2024] [Kingstinct]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# react-native-motion-activity-tracker
22

3-
[![Test Status](https://github.com/YourGithubUsername/react-native-motion-activity-tracker/actions/workflows/test.yml/badge.svg)](https://github.com/YourGithubUsername/react-native-motion-activity-tracker/actions/workflows/test.yml)
3+
[![Test Status](https://github.com/Kingstinct/react-native-motion-activity-tracker/actions/workflows/test.yml/badge.svg)](https://github.com/Kingstinct/react-native-motion-activity-tracker/actions/workflows/test.yml)
44
[![Latest version on NPM](https://img.shields.io/npm/v/react-native-motion-activity-tracker)](https://www.npmjs.com/package/react-native-motion-activity-tracker)
55
[![Discord](https://dcbadge.vercel.app/api/server/5wQGsRfS?style=flat)](https://discord.gg/5wQGsRfS)
66

@@ -32,14 +32,33 @@ A React Native library to access and track motion activity data using Apple's Co
3232

3333
{
3434
"expo": {
35-
"ios": {
36-
"infoPlist": {
37-
"NSMotionUsageDescription": "This app uses motion data to track your activity."
38-
}
39-
},
35+
"plugins": [
36+
"react-native-motion-activity-tracker"
37+
]
4038
}
4139
}
40+
4241
```
42+
3. If you want to customize the iOS permission description, you can configure it like this:
43+
{
44+
"expo": {
45+
"plugins": [
46+
[
47+
"react-native-motion-activity-tracker",
48+
{
49+
"NSMotionUsageDescription": "This app uses motion data to enhance your experience."
50+
}
51+
]
52+
]
53+
}
54+
}
55+
56+
4. After making these changes, rebuild your app to apply the native changes:
57+
58+
```bash
59+
expo prebuild
60+
```
61+
4362

4463

4564
# Examples
@@ -199,6 +218,10 @@ Checks the motion activity authorization status. Returns one of the following st
199218
- `DENIED`
200219
- `AUTHORIZED`
201220

221+
## License
222+
223+
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
224+
202225
## Contributing
203226

204227
Contributions are welcome!

app.plugin.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { createRunOncePlugin } = require("expo/config-plugins");
2+
3+
const withMotionActivityPermissions =
4+
require("./config-plugin/withMotionActivityPermissions").default;
5+
const pkg = require("./package.json");
6+
7+
module.exports = createRunOncePlugin(
8+
withMotionActivityPermissions,
9+
pkg.name,
10+
pkg.version,
11+
);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
withInfoPlist,
3+
withAndroidManifest,
4+
ConfigPlugin,
5+
} from "@expo/config-plugins";
6+
7+
const withMotionActivityPermissions: ConfigPlugin = (config) => {
8+
// Add iOS motion activity permissions
9+
config = withInfoPlist(config, (config) => {
10+
config.modResults.NSMotionUsageDescription =
11+
config.modResults.NSMotionUsageDescription ||
12+
"This app uses motion activity tracking.";
13+
return config;
14+
});
15+
16+
// Add Android Activity Recognition permission
17+
config = withAndroidManifest(config, (config) => {
18+
const androidManifest = config.modResults;
19+
const permissions = androidManifest.manifest["uses-permission"] || [];
20+
21+
// Check if the permission already exists
22+
const hasActivityRecognitionPermission = permissions.some(
23+
(permission) =>
24+
permission.$["android:name"] ===
25+
"android.permission.ACTIVITY_RECOGNITION",
26+
);
27+
28+
// Add the permission only if it doesn't exist
29+
if (!hasActivityRecognitionPermission) {
30+
permissions.push({
31+
$: { "android:name": "android.permission.ACTIVITY_RECOGNITION" },
32+
});
33+
}
34+
35+
androidManifest.manifest["uses-permission"] = permissions;
36+
return config;
37+
});
38+
39+
return config;
40+
};
41+
42+
export default withMotionActivityPermissions;

jest.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
transform: {
5+
'^.+\\.tsx?$': 'ts-jest',
6+
},
7+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
8+
testMatch: ['**/tests/**/*.test.ts', '**/tests/**/*.test.tsx'],
9+
transformIgnorePatterns: ['<rootDir>/node_modules/'],
10+
globals: {
11+
'ts-jest': {
12+
tsconfig: 'tsconfig.json',
13+
},
14+
},
15+
};
16+

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const MotionActivityTracker = require('../src/MotionActivityTrackerModule');
2+
3+
4+
jest.mock('../src/MotionActivityTrackerModule.ts', () => ({
5+
startTracking: jest.fn(() => Promise.resolve('tracking started')),
6+
stopTracking: jest.fn(() => 'tracking stopped'),
7+
getHistoricalDataIos: jest.fn((startDate, endDate) =>
8+
Promise.resolve([
9+
{ walking: true, running: false, timestamp: 123456789, confidence: 'high' },
10+
]),
11+
),
12+
addMotionStateChangeListener: jest.fn((callback) => {
13+
callback({ state: 'walking' });
14+
return { remove: jest.fn() };
15+
}),
16+
}));
17+
18+
describe('MotionActivityTracker', () => {
19+
test('should start tracking', async () => {
20+
const result = await MotionActivityTracker.startTracking();
21+
expect(result).toBe('tracking started');
22+
});
23+
24+
test('should stop tracking', () => {
25+
const result = MotionActivityTracker.stopTracking();
26+
expect(result).toBe('tracking stopped');
27+
});
28+
29+
test('should fetch historical data', async () => {
30+
const startDate = new Date(Date.now() - 1000 * 60 * 60); // 1 hour ago
31+
const endDate = new Date();
32+
const data = await MotionActivityTracker.getHistoricalDataIos(startDate, endDate);
33+
expect(data).toHaveLength(1);
34+
expect(data[0]).toHaveProperty('walking', true);
35+
});
36+
37+
test('should call listener on state change', () => {
38+
const mockListener = jest.fn();
39+
const subscription = MotionActivityTracker.addMotionStateChangeListener(mockListener);
40+
expect(mockListener).toHaveBeenCalledWith({ state: 'walking' });
41+
subscription.remove();
42+
});
43+
});
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { withInfoPlist, withAndroidManifest } from "@expo/config-plugins";
2+
import type { ExpoConfig } from "@expo/config-types";
3+
4+
import withMotionActivityPermissions from "../config-plugin/withMotionActivityPermissions";
5+
6+
interface ExpoConfigWithModResults extends ExpoConfig {
7+
modResults?: {
8+
// For iOS
9+
NSMotionUsageDescription?: string;
10+
// For Android
11+
manifest?: {
12+
"uses-permission"?: { $: { "android:name": string } }[];
13+
};
14+
};
15+
}
16+
17+
jest.mock("@expo/config-plugins", () => ({
18+
withInfoPlist: jest.fn((config, callback) => {
19+
const updatedConfig = {
20+
...config,
21+
modResults: {
22+
...config.modResults,
23+
NSMotionUsageDescription: undefined,
24+
},
25+
};
26+
const modifiedConfig = callback(updatedConfig);
27+
return {
28+
...config,
29+
modResults: { ...updatedConfig.modResults, ...modifiedConfig.modResults },
30+
};
31+
}),
32+
withAndroidManifest: jest.fn((config, callback) => {
33+
const updatedConfig = {
34+
...config,
35+
modResults: {
36+
...config.modResults,
37+
manifest: {
38+
...config.modResults?.manifest,
39+
"uses-permission": [],
40+
},
41+
},
42+
};
43+
const modifiedConfig = callback(updatedConfig);
44+
return {
45+
...config,
46+
modResults: { ...updatedConfig.modResults, ...modifiedConfig.modResults },
47+
};
48+
}),
49+
}));
50+
51+
describe("withMotionActivityPermissions", () => {
52+
it("should add iOS motion activity permissions", () => {
53+
const config: ExpoConfigWithModResults = {
54+
name: "mock-app",
55+
slug: "mock-app",
56+
modResults: {
57+
NSMotionUsageDescription: undefined,
58+
},
59+
};
60+
61+
const updatedConfig: ExpoConfigWithModResults =
62+
withMotionActivityPermissions(config);
63+
64+
expect(updatedConfig.modResults?.NSMotionUsageDescription).toBe(
65+
"This app uses motion activity tracking.",
66+
);
67+
68+
expect(withInfoPlist).toHaveBeenCalled();
69+
});
70+
71+
it("should add Android motion activity recognition permission if it does not exist", () => {
72+
const config: ExpoConfigWithModResults = {
73+
name: "mock-app",
74+
slug: "mock-app",
75+
modResults: {
76+
manifest: {
77+
"uses-permission": [],
78+
},
79+
},
80+
};
81+
82+
const updatedConfig: ExpoConfigWithModResults =
83+
withMotionActivityPermissions(config);
84+
85+
const permissions = updatedConfig.modResults?.manifest?.["uses-permission"];
86+
expect(permissions).toEqual(
87+
expect.arrayContaining([
88+
{ $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" } },
89+
]),
90+
);
91+
92+
expect(withAndroidManifest).toHaveBeenCalled();
93+
});
94+
95+
it("should not add duplicate Android motion activity recognition permission", () => {
96+
const config: ExpoConfigWithModResults = {
97+
name: "mock-app",
98+
slug: "mock-app",
99+
modResults: {
100+
manifest: {
101+
"uses-permission": [
102+
{
103+
$: { "android:name": "android.permission.ACTIVITY_RECOGNITION" },
104+
},
105+
],
106+
},
107+
},
108+
};
109+
110+
const updatedConfig: ExpoConfigWithModResults =
111+
withMotionActivityPermissions(config);
112+
113+
const permissions = updatedConfig.modResults?.manifest?.["uses-permission"];
114+
expect(permissions).toHaveLength(1); // Ensure no duplicates are added
115+
expect(permissions).toEqual(
116+
expect.arrayContaining([
117+
{ $: { "android:name": "android.permission.ACTIVITY_RECOGNITION" } },
118+
]),
119+
);
120+
121+
expect(withAndroidManifest).toHaveBeenCalled();
122+
});
123+
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"compilerOptions": {
55
"outDir": "./build"
66
},
7-
"include": ["./src"],
7+
"include": ["./src", "./tests"],
88
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
99
}

0 commit comments

Comments
 (0)