sonarfit-react-native 1.0.0

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 ADDED
@@ -0,0 +1,153 @@
1
+ # sonarfit-react-native
2
+
3
+ React Native integration for SonarFit SDK - AI-powered workout rep counting for iOS
4
+
5
+ ## Features
6
+
7
+ - AI-powered rep counting for squats, deadlifts, and bench press
8
+ - Real-time workout tracking
9
+ - Support for Apple Watch and AirPods motion sensors
10
+ - Native iOS integration with React Native bridge
11
+ - TypeScript support
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install sonarfit-react-native
17
+ ```
18
+
19
+ ### iOS Setup
20
+
21
+ 1. Install CocoaPods dependencies:
22
+ ```bash
23
+ cd ios && pod install
24
+ ```
25
+
26
+ 2. The SonarFit SDK requires iOS 17.0 or higher. Make sure your `Podfile` has:
27
+ ```ruby
28
+ platform :ios, '17.0'
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Initialize the SDK
34
+
35
+ ```typescript
36
+ import SonarFitSDK from 'sonarfit-react-native';
37
+
38
+ // Initialize with your API key when your app starts
39
+ useEffect(() => {
40
+ const initSDK = async () => {
41
+ try {
42
+ await SonarFitSDK.initialize('your-api-key-here');
43
+ console.log('SonarFit SDK initialized');
44
+ } catch (error) {
45
+ console.error('Failed to initialize SonarFit SDK:', error);
46
+ }
47
+ };
48
+
49
+ initSDK();
50
+ }, []);
51
+ ```
52
+
53
+ ### Start a Workout
54
+
55
+ ```typescript
56
+ import SonarFitSDK, { WorkoutConfig } from 'sonarfit-react-native';
57
+
58
+ const startWorkout = async () => {
59
+ const config: WorkoutConfig = {
60
+ workoutType: 'squat', // 'squat' | 'deadlift' | 'benchpress'
61
+ sets: 3,
62
+ reps: 10,
63
+ restTime: 60, // seconds
64
+ countdownDuration: 3, // seconds
65
+ autoReLift: true,
66
+ deviceType: 'none', // 'none' | 'watch' | 'airpods'
67
+ };
68
+
69
+ try {
70
+ const result = await SonarFitSDK.presentWorkout(config);
71
+
72
+ if (result.completed) {
73
+ console.log(`Completed ${result.totalRepsCompleted}/${result.totalTargetReps} reps`);
74
+ console.log(`Duration: ${result.totalDuration}s`);
75
+ console.log(`Sets:`, result.sets);
76
+ } else if (result.cancelled) {
77
+ console.log('Workout cancelled');
78
+ }
79
+ } catch (error) {
80
+ console.error('Workout error:', error);
81
+ }
82
+ };
83
+ ```
84
+
85
+ ## API Reference
86
+
87
+ ### `SonarFitSDK.initialize(apiKey: string): Promise<void>`
88
+
89
+ Initialize the SonarFit SDK with your API key.
90
+
91
+ **Parameters:**
92
+ - `apiKey`: Your SonarFit API key
93
+
94
+ ### `SonarFitSDK.presentWorkout(config: WorkoutConfig): Promise<WorkoutResult>`
95
+
96
+ Present the SonarFit workout UI.
97
+
98
+ **Parameters:**
99
+ - `config`: Workout configuration
100
+
101
+ **Returns:**
102
+ - `Promise<WorkoutResult>`: Workout results when complete or cancelled
103
+
104
+ ### Types
105
+
106
+ ```typescript
107
+ interface WorkoutConfig {
108
+ workoutType: 'squat' | 'deadlift' | 'benchpress';
109
+ sets: number;
110
+ reps: number;
111
+ restTime?: number; // default: 60
112
+ countdownDuration?: number; // default: 3
113
+ autoReLift?: boolean; // default: true
114
+ deviceType?: 'none' | 'watch' | 'airpods'; // default: 'none'
115
+ }
116
+
117
+ interface WorkoutResult {
118
+ completed: boolean;
119
+ cancelled?: boolean;
120
+ workoutType?: string;
121
+ deviceType?: string;
122
+ startTime?: number;
123
+ endTime?: number;
124
+ totalDuration?: number;
125
+ completionPercentage?: number;
126
+ targetSets?: number;
127
+ targetRepsPerSet?: number;
128
+ totalRepsCompleted?: number;
129
+ totalTargetReps?: number;
130
+ sets?: Array<{
131
+ setNumber: number;
132
+ repsCompleted: number;
133
+ }>;
134
+ }
135
+ ```
136
+
137
+ ## Requirements
138
+
139
+ - React Native >= 0.60
140
+ - iOS >= 17.0
141
+ - Xcode >= 15.0
142
+
143
+ ## Example
144
+
145
+ See the [example](./example) directory for a complete working example app.
146
+
147
+ ## License
148
+
149
+ MIT
150
+
151
+ ## Support
152
+
153
+ For issues and questions, please visit [GitHub Issues](https://github.com/sonarfit/sonarfit-react-native/issues)
@@ -0,0 +1,9 @@
1
+ //
2
+ // SonarFitReactNative-Bridging-Header.h
3
+ // SonarFitReactNative
4
+ //
5
+ // Bridging header for React Native integration
6
+ //
7
+
8
+ #import <React/RCTBridgeModule.h>
9
+ #import <React/RCTViewManager.h>
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface SonarFitReactNative : NSObject <RCTBridgeModule>
4
+
5
+ @end
@@ -0,0 +1,14 @@
1
+ #import "SonarFitReactNative.h"
2
+ #import <React/RCTBridgeModule.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(SonarFitSDKModule, NSObject)
5
+
6
+ RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
7
+ resolver:(RCTPromiseResolveBlock)resolve
8
+ rejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(presentWorkout:(NSDictionary *)config
11
+ resolver:(RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject)
13
+
14
+ @end
@@ -0,0 +1,148 @@
1
+ import Foundation
2
+ import React
3
+ import SonarFitKit
4
+
5
+ @objc(SonarFitSDKModule)
6
+ class SonarFitSDKModule: NSObject {
7
+
8
+ @objc
9
+ static func requiresMainQueueSetup() -> Bool {
10
+ return true
11
+ }
12
+
13
+ @objc
14
+ func initialize(_ apiKey: String,
15
+ resolver resolve: @escaping RCTPromiseResolveBlock,
16
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
17
+
18
+ SonarFitSDK.initialize(apiKey: apiKey) { success, error in
19
+ if success {
20
+ resolve(true)
21
+ } else {
22
+ reject("E_INIT_FAILED", "Failed to initialize SonarFit SDK: \(error?.localizedDescription ?? "Unknown error")", error)
23
+ }
24
+ }
25
+ }
26
+
27
+ @objc
28
+ func presentWorkout(_ config: NSDictionary,
29
+ resolver resolve: @escaping RCTPromiseResolveBlock,
30
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
31
+
32
+ DispatchQueue.main.async {
33
+ guard let rootViewController = RCTKeyWindow()?.rootViewController else {
34
+ reject("E_NO_ROOT_VC", "Cannot find root view controller", nil)
35
+ return
36
+ }
37
+
38
+ // Parse workout configuration
39
+ guard let workoutConfig = self.parseWorkoutConfig(config) else {
40
+ reject("E_INVALID_CONFIG", "Invalid workout configuration", nil)
41
+ return
42
+ }
43
+
44
+ // Present SonarFit workout
45
+ SonarFit.startWorkout(
46
+ config: workoutConfig,
47
+ from: rootViewController,
48
+ onCompletion: { result in
49
+ if let result = result {
50
+ // Convert WorkoutResult to dictionary
51
+ let resultDict: [String: Any] = [
52
+ "completed": result.status == .completed,
53
+ "workoutType": result.workoutType.rawValue,
54
+ "deviceType": result.deviceType.rawValue,
55
+ "startTime": result.startTime.timeIntervalSince1970,
56
+ "endTime": result.endTime.timeIntervalSince1970,
57
+ "totalDuration": result.totalDuration,
58
+ "completionPercentage": result.completionPercentage,
59
+ "targetSets": result.targetSets,
60
+ "targetRepsPerSet": result.targetRepsPerSet,
61
+ "totalRepsCompleted": result.totalRepsCompleted,
62
+ "totalTargetReps": result.totalTargetReps,
63
+ "sets": result.sets.map { set in
64
+ return [
65
+ "setNumber": set.setNumber,
66
+ "repsCompleted": set.repsCompleted
67
+ ]
68
+ }
69
+ ]
70
+ resolve(resultDict)
71
+ } else {
72
+ // Workout cancelled
73
+ resolve([
74
+ "completed": false,
75
+ "cancelled": true
76
+ ])
77
+ }
78
+ },
79
+ onPermissionError: { error in
80
+ reject("E_PERMISSION", error.localizedDescription, error)
81
+ }
82
+ )
83
+ }
84
+ }
85
+
86
+ private func parseWorkoutConfig(_ config: NSDictionary) -> WorkoutConfig? {
87
+ guard let workoutTypeString = config["workoutType"] as? String else {
88
+ return nil
89
+ }
90
+
91
+ let workoutType: WorkoutType
92
+ switch workoutTypeString.lowercased() {
93
+ case "squat":
94
+ workoutType = .squat
95
+ case "deadlift":
96
+ workoutType = .deadlift
97
+ case "benchpress":
98
+ workoutType = .benchpress
99
+ default:
100
+ return nil
101
+ }
102
+
103
+ guard let sets = config["sets"] as? Int,
104
+ let reps = config["reps"] as? Int else {
105
+ return nil
106
+ }
107
+
108
+ let restTime = config["restTime"] as? Int ?? 60
109
+ let countdownDuration = config["countdownDuration"] as? Int ?? 3
110
+ let autoReLift = config["autoReLift"] as? Bool ?? true
111
+
112
+ let deviceType: DeviceType
113
+ if let deviceTypeString = config["deviceType"] as? String {
114
+ switch deviceTypeString.lowercased() {
115
+ case "watch":
116
+ deviceType = .watch
117
+ case "airpods":
118
+ deviceType = .airpods
119
+ default:
120
+ deviceType = .none
121
+ }
122
+ } else {
123
+ deviceType = .none
124
+ }
125
+
126
+ return WorkoutConfig(
127
+ workoutType: workoutType,
128
+ sets: sets,
129
+ reps: reps,
130
+ restTime: restTime,
131
+ countdownDuration: countdownDuration,
132
+ autoReLift: autoReLift,
133
+ deviceType: deviceType
134
+ )
135
+ }
136
+ }
137
+
138
+ // Helper to get key window
139
+ func RCTKeyWindow() -> UIWindow? {
140
+ if #available(iOS 13.0, *) {
141
+ return UIApplication.shared.connectedScenes
142
+ .compactMap { $0 as? UIWindowScene }
143
+ .flatMap { $0.windows }
144
+ .first { $0.isKeyWindow }
145
+ } else {
146
+ return UIApplication.shared.keyWindow
147
+ }
148
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,93 @@
1
+ export type WorkoutType = 'squat' | 'deadlift' | 'benchpress';
2
+ export type DeviceType = 'none' | 'watch' | 'airpods';
3
+ export interface WorkoutConfig {
4
+ /** Type of workout exercise */
5
+ workoutType: WorkoutType;
6
+ /** Number of sets to complete */
7
+ sets: number;
8
+ /** Target reps per set */
9
+ reps: number;
10
+ /** Rest time between sets in seconds (default: 60) */
11
+ restTime?: number;
12
+ /** Countdown before each set in seconds (default: 3) */
13
+ countdownDuration?: number;
14
+ /** Automatically start next set after countdown (default: true) */
15
+ autoReLift?: boolean;
16
+ /** Device type to use for motion tracking (default: 'none' for auto-detect) */
17
+ deviceType?: DeviceType;
18
+ }
19
+ export interface WorkoutSet {
20
+ /** Set number (1-indexed) */
21
+ setNumber: number;
22
+ /** Number of reps completed in this set */
23
+ repsCompleted: number;
24
+ }
25
+ export interface WorkoutResult {
26
+ /** Whether the workout was completed successfully */
27
+ completed: boolean;
28
+ /** Whether the workout was cancelled by user */
29
+ cancelled?: boolean;
30
+ /** Type of workout performed */
31
+ workoutType?: WorkoutType;
32
+ /** Device used for motion tracking */
33
+ deviceType?: DeviceType;
34
+ /** Workout start timestamp (Unix epoch) */
35
+ startTime?: number;
36
+ /** Workout end timestamp (Unix epoch) */
37
+ endTime?: number;
38
+ /** Total workout duration in seconds */
39
+ totalDuration?: number;
40
+ /** Percentage of target reps completed (0.0 to 1.0) */
41
+ completionPercentage?: number;
42
+ /** Target number of sets */
43
+ targetSets?: number;
44
+ /** Target reps per set */
45
+ targetRepsPerSet?: number;
46
+ /** Total reps completed across all sets */
47
+ totalRepsCompleted?: number;
48
+ /** Total target reps for the workout */
49
+ totalTargetReps?: number;
50
+ /** Array of completed sets with rep counts */
51
+ sets?: WorkoutSet[];
52
+ }
53
+ export declare class SonarFitSDK {
54
+ /**
55
+ * Initialize the SonarFit SDK with your API key
56
+ * @param apiKey Your SonarFit API key
57
+ * @returns Promise that resolves when initialization is complete
58
+ * @throws Error if native module is not available or initialization fails
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * await SonarFitSDK.initialize('your-api-key-here');
63
+ * ```
64
+ */
65
+ static initialize(apiKey: string): Promise<void>;
66
+ /**
67
+ * Present the SonarFit workout UI modally
68
+ * @param config Workout configuration
69
+ * @returns Promise that resolves with workout result when workout completes or is cancelled
70
+ * @throws Error if native module is not available or workout fails
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const result = await SonarFitSDK.presentWorkout({
75
+ * workoutType: 'squat',
76
+ * sets: 3,
77
+ * reps: 10,
78
+ * restTime: 60
79
+ * });
80
+ *
81
+ * if (result.completed) {
82
+ * console.log(`Completed ${result.totalRepsCompleted}/${result.totalTargetReps} reps`);
83
+ * }
84
+ * ```
85
+ */
86
+ static presentWorkout(config: WorkoutConfig): Promise<WorkoutResult>;
87
+ /**
88
+ * Check if SonarFit SDK is available on this platform
89
+ * @returns true if the native module is available, false otherwise
90
+ */
91
+ static isAvailable(): boolean;
92
+ }
93
+ export default SonarFitSDK;
package/lib/index.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SonarFitSDK = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const { SonarFitSDKModule } = react_native_1.NativeModules;
6
+ class SonarFitSDK {
7
+ /**
8
+ * Initialize the SonarFit SDK with your API key
9
+ * @param apiKey Your SonarFit API key
10
+ * @returns Promise that resolves when initialization is complete
11
+ * @throws Error if native module is not available or initialization fails
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * await SonarFitSDK.initialize('your-api-key-here');
16
+ * ```
17
+ */
18
+ static async initialize(apiKey) {
19
+ if (!SonarFitSDKModule) {
20
+ throw new Error('SonarFitSDK native module not found. Make sure you have installed the native dependencies.');
21
+ }
22
+ try {
23
+ await SonarFitSDKModule.initialize(apiKey);
24
+ }
25
+ catch (error) {
26
+ throw new Error(`SonarFit initialization failed: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
27
+ }
28
+ }
29
+ /**
30
+ * Present the SonarFit workout UI modally
31
+ * @param config Workout configuration
32
+ * @returns Promise that resolves with workout result when workout completes or is cancelled
33
+ * @throws Error if native module is not available or workout fails
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const result = await SonarFitSDK.presentWorkout({
38
+ * workoutType: 'squat',
39
+ * sets: 3,
40
+ * reps: 10,
41
+ * restTime: 60
42
+ * });
43
+ *
44
+ * if (result.completed) {
45
+ * console.log(`Completed ${result.totalRepsCompleted}/${result.totalTargetReps} reps`);
46
+ * }
47
+ * ```
48
+ */
49
+ static async presentWorkout(config) {
50
+ if (!SonarFitSDKModule) {
51
+ throw new Error('SonarFitSDK native module not found. Make sure you have installed the native dependencies.');
52
+ }
53
+ try {
54
+ const result = await SonarFitSDKModule.presentWorkout(config);
55
+ return result;
56
+ }
57
+ catch (error) {
58
+ throw new Error(`SonarFit workout failed: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
59
+ }
60
+ }
61
+ /**
62
+ * Check if SonarFit SDK is available on this platform
63
+ * @returns true if the native module is available, false otherwise
64
+ */
65
+ static isAvailable() {
66
+ return SonarFitSDKModule != null;
67
+ }
68
+ }
69
+ exports.SonarFitSDK = SonarFitSDK;
70
+ exports.default = SonarFitSDK;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "sonarfit-react-native",
3
+ "version": "1.0.0",
4
+ "description": "React Native integration for SonarFit SDK - AI-powered workout rep counting",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepare": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "react-native",
13
+ "fitness",
14
+ "workout",
15
+ "ai",
16
+ "motion-tracking",
17
+ "rep-counting",
18
+ "sonarfit"
19
+ ],
20
+ "author": "SonarFit",
21
+ "license": "MIT",
22
+ "homepage": "https://github.com/sonarfit/sonarfit-react-native",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/sonarfit/sonarfit-react-native.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/sonarfit/sonarfit-react-native/issues"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=16.8.0",
32
+ "react-native": ">=0.60.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.0.0",
36
+ "@types/react-native": "^0.72.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "files": [
40
+ "lib/",
41
+ "ios/",
42
+ "android/",
43
+ "sonarfit-react-native.podspec",
44
+ "README.md"
45
+ ],
46
+ "react-native": "lib/index.js"
47
+ }
@@ -0,0 +1,24 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "sonarfit-react-native"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => "17.0" }
14
+ s.source = { :git => "https://github.com/sonarfit/sonarfit-react-native.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
17
+ s.requires_arc = true
18
+
19
+ s.dependency "React-Core"
20
+ s.dependency "SonarFitKit-Vendored"
21
+
22
+ # Swift version
23
+ s.swift_version = "5.9"
24
+ end