Skip to content

Commit

Permalink
feat: add pedometer data updates
Browse files Browse the repository at this point in the history
  • Loading branch information
francisli committed Jan 19, 2024
1 parent 084390c commit 45fbb8c
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 52 deletions.
2 changes: 2 additions & 0 deletions example/ios/CmPedometerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 2XU846A9M4;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = CmPedometerExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -493,6 +494,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 2XU846A9M4;
INFOPLIST_FILE = CmPedometerExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
2 changes: 2 additions & 0 deletions example/ios/CmPedometerExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSMotionUsageDescription</key>
<string>To track your steps.</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
Expand Down
99 changes: 58 additions & 41 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

import { StyleSheet, View, Text } from 'react-native';
import { StyleSheet, View, Text, Button } from 'react-native';
import {
CMAuthorizationStatus,
authorizationStatus,
Expand All @@ -10,63 +10,80 @@ import {
isPaceAvailable,
isCadenceAvailable,
isPedometerEventTrackingAvailable,
CMPedometerData,
startUpdates,
stopUpdates,
} from 'react-native-cm-pedometer';

export default function App() {
const [authorizationStatusResult, setAuthorizationStatusResult] =
React.useState<CMAuthorizationStatus | undefined>();
const [isStepCountingAvailableResult, setStepCountingAvailableResult] =
React.useState<boolean | undefined>();
const [isDistanceAvailableResult, setDistanceAvailableResult] =
React.useState<boolean | undefined>();
const [isFloorCountingAvailableResult, setFloorCountingAvailableResult] =
React.useState<boolean | undefined>();
const [isPaceAvailableResult, setPaceAvailableResult] = React.useState<
boolean | undefined
const [status, setStatus] = React.useState<
CMAuthorizationStatus | undefined
>();
const [isCadenceAvailableResult, setCadenceAvailableResult] = React.useState<
const [isStepAvail, setStepAvail] = React.useState<boolean | undefined>();
const [isDistAvail, setDistAvail] = React.useState<boolean | undefined>();
const [isFloorAvail, setFloorAvail] = React.useState<boolean | undefined>();
const [isPaceAvail, setPaceAvail] = React.useState<boolean | undefined>();
const [isCadenceAvail, setCadenceAvail] = React.useState<
boolean | undefined
>();
const [
isPedometerEventTrackingAvailableResult,
setPedometerEventTrackingAvailableResult,
] = React.useState<boolean | undefined>();
const [isEventAvail, setEventAvail] = React.useState<boolean | undefined>();

React.useEffect(() => {
authorizationStatus().then(setAuthorizationStatusResult);
isStepCountingAvailable().then(setStepCountingAvailableResult);
isDistanceAvailable().then(setDistanceAvailableResult);
isFloorCountingAvailable().then(setFloorCountingAvailableResult);
isPaceAvailable().then(setPaceAvailableResult);
isCadenceAvailable().then(setCadenceAvailableResult);
isPedometerEventTrackingAvailable().then(
setPedometerEventTrackingAvailableResult
);
authorizationStatus().then(setStatus);
isStepCountingAvailable().then(setStepAvail);
isDistanceAvailable().then(setDistAvail);
isFloorCountingAvailable().then(setFloorAvail);
isPaceAvailable().then(setPaceAvail);
isCadenceAvailable().then(setCadenceAvail);
isPedometerEventTrackingAvailable().then(setEventAvail);
return () => {
stopUpdates();
};
}, []);

const [isDataStarted, setDataStarted] = React.useState<boolean>(false);
const [data, setData] = React.useState<CMPedometerData | undefined>();
const [error, setError] = React.useState<Error | undefined>();
function onPressData() {
if (isDataStarted) {
stopUpdates();
} else {
startUpdates(new Date(), (newError, newData) => {
setError(newError);
setData(newData);
});
}
setDataStarted(!isDataStarted);
}

return (
<View style={styles.container}>
<Text>
Authorization Status:{' '}
{authorizationStatusResult !== undefined &&
CMAuthorizationStatus[authorizationStatusResult]}
</Text>
<Text>
Is Step Counting Available: {isStepCountingAvailableResult?.toString()}
</Text>
<Text>
Is Distance Available: {isDistanceAvailableResult?.toString()}
</Text>
<Text>
Is Floor Counting Available:{' '}
{isFloorCountingAvailableResult?.toString()}
{status !== undefined && CMAuthorizationStatus[status]}
</Text>
<Text>Is Pace Available: {isPaceAvailableResult?.toString()}</Text>
<Text>Is Cadence Available: {isCadenceAvailableResult?.toString()}</Text>
<Text>Is Step Counting Available: {isStepAvail?.toString()}</Text>
<Text>Is Distance Available: {isDistAvail?.toString()}</Text>
<Text>Is Floor Counting Available: {isFloorAvail?.toString()}</Text>
<Text>Is Pace Available: {isPaceAvail?.toString()}</Text>
<Text>Is Cadence Available: {isCadenceAvail?.toString()}</Text>
<Text>
Is Pedometer Event Tracking Available:{' '}
{isPedometerEventTrackingAvailableResult?.toString()}
Is Pedometer Event Tracking Available: {isEventAvail?.toString()}
</Text>
<Button
onPress={onPressData}
title={isDataStarted ? 'Stop Data Updates' : 'Start Data Updates'}
/>
<Text>Start Date: {data?.startDate.toLocaleString()}</Text>
<Text>End Date: {data?.endDate.toLocaleString()}</Text>
<Text>Number of Steps: {data?.numberOfSteps}</Text>
<Text>Distance: {data?.distance}</Text>
<Text>Current Cadence: {data?.currentCadence}</Text>
<Text>Current Pace: {data?.currentPace}</Text>
<Text>Average Active Pace: {data?.averageActivePace}</Text>
<Text>Floors Ascended: {data?.floorsAscended}</Text>
<Text>Floors Descended: {data?.floorsDescended}</Text>
<Text>Error: {error?.message}</Text>
</View>
);
}
Expand Down
17 changes: 15 additions & 2 deletions ios/CmPedometer.mm
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(CmPedometer, NSObject)
@interface RCT_EXTERN_MODULE(CmPedometer, RCTEventEmitter)

// MARK: - Determining Pedometer Availability

RCT_EXTERN_METHOD(authorizationStatus:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
Expand All @@ -23,9 +26,19 @@ @interface RCT_EXTERN_MODULE(CmPedometer, NSObject)
RCT_EXTERN_METHOD(isPedometerEventTrackingAvailable:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)

// MARK: - Gathering Live Pedometer Data

RCT_EXTERN_METHOD(startUpdates:(NSString *)from)

RCT_EXTERN_METHOD(stopUpdates)

// MARK: - Fetching Historical Pedometer Data

// MARK: -

+ (BOOL)requiresMainQueueSetup
{
return NO;
return NO;
}

@end
74 changes: 66 additions & 8 deletions ios/CmPedometer.swift
Original file line number Diff line number Diff line change
@@ -1,39 +1,97 @@
import CoreMotion
import React

enum CmPedometerEvent: String, CaseIterable {
case onPedometerData
case onPedometerEvent
}

@objc(CmPedometer)
class CmPedometer: NSObject {
class CmPedometer: RCTEventEmitter {
static let instance = CMPedometer()
static let dateFormatter = {
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
return dateFormatter
}()

// MARK: - Determining Pedometer Availability

@objc(authorizationStatus:withRejecter:)
func authorizationStatus(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func authorizationStatus(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.authorizationStatus().rawValue)
}

@objc(isStepCountingAvailable:withRejecter:)
func isStepCountingAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isStepCountingAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isStepCountingAvailable())
}

@objc(isDistanceAvailable:withRejecter:)
func isDistanceAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isDistanceAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isDistanceAvailable())
}

@objc(isFloorCountingAvailable:withRejecter:)
func isFloorCountingAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isFloorCountingAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isFloorCountingAvailable())
}

@objc(isPaceAvailable:withRejecter:)
func isPaceAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isPaceAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isPaceAvailable())
}

@objc(isCadenceAvailable:withRejecter:)
func isCadenceAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isCadenceAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isCadenceAvailable())
}

@objc(isPedometerEventTrackingAvailable:withRejecter:)
func isPedometerEventTrackingAvailable(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
func isPedometerEventTrackingAvailable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
resolve(CMPedometer.isPedometerEventTrackingAvailable())
}

// MARK: - Gathering Live Pedometer Data

@objc(startUpdates:)
func startUpdates(from: String) -> Void {
if let from = CmPedometer.dateFormatter.date(from: from) {
CmPedometer.instance.startUpdates(from: from) { [weak self] (data, error) in
guard let self = self else { return }
var body: [String: Any] = [:]
body["error"] = error?.localizedDescription
if let data = data {
body["data"] = [
"startDate": CmPedometer.dateFormatter.string(from: data.startDate),
"endDate": CmPedometer.dateFormatter.string(from: data.endDate),
"numberOfSteps": data.numberOfSteps,
"distance": data.distance as Any,
"averageActivePace": data.averageActivePace as Any,
"currentPace": data.currentPace as Any,
"currentCadence": data.currentCadence as Any,
"floorsAscended": data.floorsAscended as Any,
"floorsDescended": data.floorsDescended as Any,
]
}
self.sendEvent(withName: CmPedometerEvent.onPedometerData.rawValue, body: body)
}
}
}

@objc(stopUpdates)
func stopUpdates() -> Void {
CmPedometer.instance.stopUpdates()
}

// MARK: - Fetching Historical Pedometer Data

// MARK: -

override func supportedEvents() -> [String]! {
return CmPedometerEvent.allCases.map{ $0.rawValue }
}
}
57 changes: 56 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NativeModules, Platform } from 'react-native';
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';

const LINKING_ERROR =
`The package 'react-native-cm-pedometer' doesn't seem to be linked. Make sure: \n\n` +
Expand All @@ -17,6 +17,8 @@ const CmPedometer = NativeModules.CmPedometer
}
);

// Determining Pedometer Availability

export enum CMAuthorizationStatus {
notDetermined,
restricted,
Expand Down Expand Up @@ -51,3 +53,56 @@ export function isCadenceAvailable(): Promise<boolean> {
export function isPedometerEventTrackingAvailable(): Promise<boolean> {
return CmPedometer.isPedometerEventTrackingAvailable();
}

// Getting Live Pedometer Data

export interface CMPedometerData {
startDate: Date;
endDate: Date;
numberOfSteps: number;
distance: number | undefined | null;
averageActivePace: number | undefined | null;
currentPace: number | undefined | null;
currentCadence: number | undefined | null;
floorsAscended: number | undefined | null;
floorsDescended: number | undefined | null;
}

const enum CmPedometerEvent {
onPedometerData = 'onPedometerData',
onPedometerEvent = 'onPedometerEvent',
}

const eventEmitter = new NativeEventEmitter(CmPedometer);

export function startUpdates(
from: Date,
withHandler: (
error: Error | undefined,
data: CMPedometerData | undefined
) => void
): void {
eventEmitter.addListener(CmPedometerEvent.onPedometerData, (event: any) => {
let error: Error | undefined;
if (event.error) {
error = new Error(event.error);
}
let data: CMPedometerData | undefined;
if (event.data) {
data = {
...event.data,
startDate: new Date(event.data.startDate),
endDate: new Date(event.data.endDate),
} as CMPedometerData;
}
withHandler(error, data);
});
CmPedometer.startUpdates(from.toISOString());
}

export function stopUpdates(): void {
eventEmitter.removeAllListeners(CmPedometerEvent.onPedometerData);
CmPedometer.stopUpdates();
}

// Fetching Historical Pedometer Data

0 comments on commit 45fbb8c

Please sign in to comment.